Java多线程、常用类、枚举类、注解、集合、泛型、IO、反射

文章目录

多线程

java.lang.Thread

  • 每个线程都是通过特定Thread对象的run()方法来完成操作的,经常把run()方法的主体成为线程体
  • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
class PrimeThread extends Thread {
    long minPrime;

    public PrimeThread(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        //compute primes larger than minPrime
        long n = minPrime + 1;
        while (!isPrime(n)) {
            n++;
        }
        System.out.println(n);
    }

    Boolean isPrime(long n) {
        if (n < 2) return false;
        if (n == 2) return true;
        if (n % 2 == 0) return false;
        for (int i = 3; (long) i * i <= n; i += 2)
            if (n % i == 0) return false;
        return true;
    }
}

public class T {
    @Test
    public void tsest1() {
        PrimeThread primeThread = new PrimeThread(73);
        primeThread.start();
    }
}
  • 程序(program):是为完成特定任务、有某种语言编写的一组指令的集合。即指一段静态代码,静态对象
  • 进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程
  • 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径

创建(要求熟练)

继承于Thread类

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run()
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start()【启动当前线程;调用当前线程的run()】
//例
class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i + this.getName());
            }
        }
    }
}

class ThreadTest {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i + "--main");
            }
        }
        //匿名类的匿名对象
        new Thread() {
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();
    }
}
  • 通过run()调用的仍是main线程
  • 已经启用过的线程的对象不可再次启动,想要再次使用该线程只能重新new一个对象
Thread类中的常用方法
方法作用
start()启动当前线程;调用当前线程的run()
run()通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明再此方法中
currentThread()返回执行当前代码的线程
currentThread().getName()获取当前线程的名字
setName()设置当前线程的名称
yield()释放当时cpu的执行权
join()再线程a中调用线程b的join(),此线程a就进入阻塞状态,知道线程b执行完后,线程a才结束阻塞方法
stop()强制结束线程,已过时
sleep(long millis)让当前线程“睡眠”,以毫秒为单位。再millitime时间内当前线程是阻塞状态1000ms=1s
isAlive()判断线程是否存活,返回boolrean
//例
class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + i);
                if (i % 9 == 0) {
                    yield();
                }
            }
        }
    }

    public MyThread(String name) {//利用构造器命名
        super(name);
    }
}

class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread("Thread--");
//        t.setName("分线程一");
        t.start();
        Thread.currentThread().setName("主线程");
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + i);
            }
            if (i == 20) {
                t.join();
            }
        }
    }
}

实现Runnable接口

非静态的同步方法,同步监视器是:this

静态方法的同步方法,同步监视器是:当前类本身

  1. 创建一个实现了Runnable接口的类
  2. 实现类去实现Runnable中的抽象方法run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
//例
class MyThread implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i + this.getClass().getName());
        }
    }
}

class ThreadTest {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        Thread t = new Thread(m);
        t.start();
    }
}

接口与继承的对比

开发中优先选择实现Runnable接口的方式

  1. 实现的方式没有类的单继承的局限性
  2. 实现的方式更适合来处理多个线程有共享数据的情况
  • 两种方式都需要重写run(),将线程要执行的逻辑声明再run()中
  • 目前两种方式,想要启动线程,都是调用的Thread类中的start()

线程的优先级

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5

范围:1~10(默认5)

方法用途
getPriority()获取线程优先值
setPriority(int newPriority)设置线路的优先级

生命周期

JDK中用Thread.State类定义了线程的集中状态

  • NEW
  • RUNNABLE
  • BLOCKED
  • WATING
  • TIMED_WAITING
  • TERMINATED

在这里插入图片描述

阻塞:临时状态,不可作为最终状态

死亡:最终状态

线程的安全

/**
 * 解决买票过程中,出现了重票、错票(线程安全的问题)
 * 下边的代码模拟窗口售票100张
 */
class MyThread implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

class ThreadTest {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        Thread t = new Thread(m);
        t.start();
    }
}

同步代码块

  • 优点:解决了线程的安全问题
  • 局限性:操作同步代码时,智能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低
synchronized(同步监视器){//同步监视器:俗称锁,任何一个类的对象都可以充当锁;多个线程必须要共用”同一把锁“
    //需要同步的代码(操作共享数据的代码)
}
class MyThread implements Runnable {
    private int ticket = 1000;
    Object obj = new Object();//必须保证这个对象三个线程都共用,需保证其唯一性

    //    Dog dog=new Dog//也可行,不要求一定要object
    @Override
    public void run() {
        while (true) {
            synchronized (obj) {//仅包含更改数据的代码即可,不可多,也不可少
                //synchronized (this){//用Runable接口实现的可以用this,此时this指定的是”MyThread“new的对象
                //使用继承的方式不可更换(慎用)为this,此时this指定不同对象
                //syschronized (MyThread.class){//也可以把类当对象【Class c=MyThread.class】,且只会加载一次(唯一)
                //MyTHread.class也可以再继承于Thread的类的方法
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "售出票号:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        Thread t = new Thread(m);
        Thread t1 = new Thread(m);
        Thread t2 = new Thread(m);
        t.start();
        t1.start();
        t2.start();
    }
}

同步方法

  • 如果共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
  • 此时同步监视器为this。使用继承的方式实现多线程是需要保证synchronized为静态方法(此时同步监视器为MyThread.class)
class MyThread implements Runnable {
    private int ticket = 1000;

    @Override
    public void run() {
        boolean t = true;
        while (t) {
            t = c();
        }
    }

    private synchronized boolean c() {//此时同步监视器为this
//  private static synchronized boolean c() {//此时同步监视器为MyThread.class
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "售出票号:" + ticket);
            ticket--;
            return true;
        } else {
            return false;
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        Thread t = new Thread(m);
        Thread t1 = new Thread(m);
        Thread t2 = new Thread(m);
        t.start();
        t1.start();
        t2.start();
    }
}

总结

同步方法仍然设计到同步监视器,只是不需要我们显式的声明。

非静态的同步方法,同步监视器是:this

静态的同步方法,同步监视器是:当前类本身

优化懒汉模式为线程安全的

使用同步机制将单例模式中懒汉式改写为线程安全的

public class SingletonTest {
    public static void main(String[] args) {
        Order order1 = Order.getInstance();
        Order order2 = Order.getInstance();
        System.out.println(order1 == order2);//true
    }
}

class Order {

    // 私有化类的构造器
    private Order() {
    }

    //声明当前类的对象,没有初始化
    //此对象也需声明为static
    private static Order instance = null;

    //声明public、static的返回当前类对象的方法
    //public static synchronized Order getInstance(){//接口或继承都可用--同步方法
    public static Order getInstance() {
        /*接口或继承都可用--同步方法块--效率稍差
        synchronized(Order.class){
        if(instance==null){
            instance =new Order();
        }
        return instance;
        }*/

//        接口或继承都可用--同步方法块--效率稍高
        if (instance == null) {
            synchronized (Order.class) {
                if (instance == null) {
                    instance = new Order();
                }
            }
        }
        return instance;
    }
}

Lock(锁)的方式解决线程安全的问题

  • 从JDK 5.0开始,java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具
  • ReentranLock类实现了Lock。
import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable {
    private int ticket = 100;
    //实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();//若是ReentrantLock(true)的话就是启动公平锁,严格按每个线程顺序依次执行
    //若是按继承的方式实现的多线程,此句还需要价格static

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                //调用锁定方法lock()
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "售出票号:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                lock.unlock();
                //调用解锁方法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.start();
        t2.start();
        t3.start();
    }
}

synchronized与Lock的异同

    • 解决了线程的安全问题
    • synchronized机制在执行完相应的同步代码块以后,自动的释放同步监视器
    • Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的实现(unlock())
  • 建议使用顺序

    Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

死锁

  • 不同的线程分别占用对方需要的同步资源不放,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
//死锁举例
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread() {
            @Override
            public void run() {
                synchronized (s1) {
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2) {
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2) {
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1) {
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

解决方法

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量比卖你嵌套同步

线程的通讯

方法作用
wait();一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify();一旦执行此方法,唤醒被wait()的一个线程
若有多个线程被wait,就唤醒优先级高的那个
notify();一旦执行次此方法,就会唤醒所有被wait的线程。
  • wait()、notify()、notify()三个方法只能使用在同步代码块和同步方法中,在Lock锁里会报错
  • wait()、notify()、notify()这三个方法的调用者必须是同步代码块或同步方法当中的同步监视器
  • wait()、notify()、notify()三个方法是定义在java.lang.Object类中
//两个线程依次打印100以内的自然数
class Number implements Runnable {
    private int number = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                this.notify();//拿锁
                //notify();//在唤醒多个线程时可以使用
                if (number <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                    try {
                        this.wait();//调用该方法的线程进入阻塞
                        //释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

wait()与sleep()的异同

    1. 一旦执行方法,都可以使得当前线程进入阻塞状态
    1. 两个方法声明的位置不同
      • Thread类中声明sleep()
      • Object类中声明wait()
    2. 调用的要求不同
      • sleep()可以在任何需要的场景下调用
      • wait()必须使用在同步代码块或同步方法中
    3. 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,
      • sleep()不会释放锁,
      • wait()会释放锁。

生产者/消费者问题

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品

question[1] 生产者比消费者快时,消费者会漏掉一些数据没有取到

question[2] 消费者比生产者快时,消费者会取相同的数据

package com.test;

class Clerk {
    private int productCount = 0;

    public synchronized void produceProduct() {//生产产品
        if (productCount < 20) {
            productCount++;
            System.out.println(Thread.currentThread().getName() + "\t开始生产第" + productCount + "个产品");
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void consumptionPoduct() {//消费产品
        if (productCount > 0) {
            System.out.println(Thread.currentThread().getName() + "开始消费第" + productCount + "个产品");
            productCount--;
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer extends Thread {//生产者
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName() + "\t开始生产产品");
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}

class Consumer extends Thread {//消费者
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName() + "\t开始消费产品");
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumptionPoduct();
        }
    }
}

public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");
        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");
        p1.start();
        c1.start();
    }
}

jdk5新增的线程创建方式(要求会写)

实现Callable接口

  1. 创建一个实现Callable的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中(需要有返回值;若不需返回值可返回null)
  3. 创建Callable接口实现类的对象
  4. 将此Callable接口实现类的对象传递到FutureTask构造器中,创建FutureTask的对象
  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
  6. 获取Callable中call方法的返回值
  • 与Runnable相比,Callable功能更强大些
    1. 相比run()方法,可以有返回值
    2. 方法可以抛出异常
    3. 支持泛型的返回值
    4. 需要借助FutureTask类,比如获取返回结果
  • Future接口
    1. 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
    2. FutrueTask是Futrue接口的唯一的实现类
    3. FutrueTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Futrue得到Callable的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

//实现Callable接口
class NumThread implements Callable {
    public Object call() {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        NumThread numThread = new NumThread();
        FutureTask futureTask = new FutureTask(numThread);
        new Thread(futureTask).start();
        try {
            //get()返回值即为FutureTask构造器参数Callable对象实现类重写的call()方法的返回值
            Object sum = futureTask.get();//获取返回值
            System.out.println("总和为" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

为何Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

  1. call()可以有返回值
  2. call()可以抛出异常,被外面的操作捕获,获取异常信息
  3. Callable支持泛型

使用线程池

  1. 提高响应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
  3. 便于线程管理
  • corePoolSize:核心池的大小
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程没有任务时最多保持多长时间后会终止

JDK 5.0起提供了线程池相关API:ExecutorServeice和Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExector

  • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
  • Future submit(Callabletask):执行任务,有返回值,一般又来执行Callable
  • void shutdown():关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

  • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  • Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
  • Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
  • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或定期地执行
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

class NumberThread implements Runnable {
    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) {
        //提供指定线程数量给的线程池子
        ExecutorService service = Executors.newFixedThreadPool(10);//建一个线程池,包含10个线程
        //执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适用于Runnable
//      service.submit(Callable callable);//适用于Callable
//      FutureTask futureTask=new FutureTask(callable);//Callable后续步骤1
//		Object newVariable=futureTask.get();//Callable后续步骤2
        service.shutdown();

        ThreadPoolExecutor service01 = (ThreadPoolExecutor) service;//向下转型
        service01.setCorePoolSize(15);//线程数
//        service01.setKeepAliveTime();//活跃时间
    }
}

常用类

字符串

String类

  • String类:代表字符串。Java程序中的所有字符串字面值都作为此类的实例实现
  • String是一个final类,代表不可变的字符序列
  • 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
  • String对象的字符内容是存储在一个字符数组value[]中的
/*
String:字符串,使用一对""引起来表示
String
	实现了Serializable接口:表示字符串是支持序列化
	实现了Comparable接口:表示String可以比较大小
String内部定义了final char[] value用于存储字符串数据
String:代表不可变的字符序列。简称:不可变性
	当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值
	当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
	当调用String的replace()方法修改指定的字符或字符串时,不能使用原有的value进行赋值
通过字面量(区别于new)的方式给字符串赋值,此时的字符串值声明在字符串常量池中
字符串常量池中是不会存储相同内容的字符串的
*/

import org.junit.Test;

public class StringTest {
    @Test
    public void test() {
        String s1 = "abc";//字面量的定义方式
        String s2 = "abc";
        String s3 = "abc";
        System.out.println(s1 == s2);//比较地址值;true
        s1 = "Hello";
        System.out.println(s1 == s2);//比较地址值;false
        s3 += "def";//abc后接上def;但s2不会变

        String s4 = "abc";
        String s5 = s4.replace('a', 'm');//将s4中所有a变为m
        System.out.println(s5);
    }
}
不可变性

String:代表不可变的字符序列。简称:不可变性
当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值
当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
当调用String的replace()方法修改指定的字符或字符串时,不能使用原有的value进行赋值

实例化
  1. 通过字面量定义的方式

    String a = "abc";
    String b = "abc";
    System.out.println(a==b);//true
    System.out.println(a.equals(b));//true
    //声明在方法区中的字符串常量池中
    //“==”比较的是地址值
    
  2. 通过new+构造器的方式

    String a = new String("abc");
    String b = new String("abc");
    System.out.println(a == b);     //false
    System.out.println(a.equals(b));//true
    //此时a保存的地址值,是数据在堆空间中开辟空间以后对应的地址值
    //但此时a的value指向常量池
    
拼接操作
  • 常量与常量拼接的结果在常量池。且常量池中不会存在相同内容的常量

  • 只要其中有一个是变量,结果就在堆中

  • 如果拼接的结果调用intern()方法,返回值就在常量池中

String s1 = "hello";//常量池
String s2 = "world";//常量池

String s3 = "hello" + "world";//常量池
String s4 = s1 + "world";//堆
String s5 = s1 + s2;//堆
String s6 = (s1 + s2).intern();//常量池
//intern()的返回值是在方法区的该值

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

常用方法

  • int length():返回字符串的长度。return value.length
  • char charAt(int index):返回某索引处的字符。reurn value.[index]
  • booblean isEmpty():判断是否是空字符串。return value.length==0
  • String toLowerCase():使用默认语言环境,将String中的所有字符转换为小写
  • String toUpperCase():使用默认语言环境,将String中的所有字符转换为大写
  • String trim():返回字符串的副本,忽略前导空白和尾部空白
  • boolean equals(Object obj):比较字符串的内容是否相同
  • booblean equalsgnoreCase(String anotherString):与equals方法类似,忽略大小写
  • String concat(String str):将指定字符串连接到此字符串的结尾。等价于用“+”
  • int compareTo(String anotherString):比较两个字符串的大小
  • String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后一个子字符串
  • String substring(int beginIndex,int endIndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个字符串
  • booblea endsWith(String suffix):测试此字符串是否以指定的后缀结束
  • boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
  • boolean startsWith(String prefix,int toffset):测试此字符串从指定索引开始的字符串是否以指定前缀开始
  • boolean contains(CharSequence s):当且仅当此字符串包含指定的char值序列时,返回true
  • int indexOf(String str):返回子字符串在此字符串中第一次出现处的索引;未找到返回-1
  • int indexOf(String str,int fromIndex):返回指定子字符串再次字符串中第一次出现的索引,从指定的索引开始
  • int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引;未找到返回-1
  • int lastIndexOf(String str,int fromIndex):返回指定子字符串在此字符串中最后一次出现的索引,从指定的索引开始反向搜索
  • String replace(char oldChar,char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。
  • 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个,如果超过了,剩下的全部都放到最后一个元素中

String与基本数据类型、包装类之间的转换

/*
String -->基本数据类型、包装类:调用包装类的静态方法
parseXxx(str)
*/
String str = "123";
int num = Integer.parseInt(str);
System.out.println(str + 1);//1231	String类型,“+”执行字符拼接
System.out.println(num + 1);//124	int类型,“+”执行算术运算
/*
基本数据类型、包装类 -->String:调用String重载的
valueOf(xxx)
*/
int num = 123;
String str = String.valueOf(num);//在方法区
//String str=numm+"";		//值和“str"一样,但在堆中
System.out.println(num + 1);//124     int类型,“+”执行算术运算
System.out.println(str + 1);//1231    String类型,“+”执行字符拼接

String与char[]转换

/*
String --> char[]:调用String的toCharArray()
*/
String str="123";
char [] charArray=str.toCharArray();
/*
char[] --> String:调用String的构造器
*/
char[] arr=new char[]{'h','e','l','l','o'};
String str=new String(arr);//在堆中

String与byte[]之间的转换

/*
byte[] -->String
String(byte[]):平台的默认tinng符集解码指定的byte数组,构造新的String
String(byte[],int offset,int length):用指定的字节数组的一部分,即从数组开始位置offset开始取length个字节构造一个字符串对象
*/

byte[] bytes = new byte[]{104, 101, 108, 108, 111, 49, 50, 51, -28, -67, -96, -27, -91, -67};
String str = new String(bytes);//使用默认字符集解码
//若出现乱码。==》编码集和解码集不一致!
//String str =new String(gbks,"gbk");//使用指定字符集(gbk)解码
/*
String --> byte[]
public byte[] getBytes():使用平台的默认字符集将此String编码为byte序列,并将结构存储到一个新的byte数组中
public byte[] getBytes(String charsetName):使用指定的字符集将此String编码到byte序列,并将结构存储到新的byte数组
*/
String str="hello123你好";
byte[] bytes=str.getBytes();//默认的字符集编码
//byte[] gbks=str.getBytes("gbk");//指定字符集(gbk)编码

stringBuffer

java.lang.StringBuffer 代表可变的字符序列,JDK1.0中声明,可以堆字符串内容进行增删,此时不会产生新的对象

很多方法与String相同

作为参数船体是,方法内部可以改变值

/*
常用方法
StringBuffer append():提供了很多的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():把当前字符序列逆转
*/
  • 当append和insert时,如果原来value数组长度不够,可扩容

  • 如上这些方法支持方法链操作

  • 方法链的原理

    @Override
    public StringBuilder append(String str){
        super.append(str);
        return this;
    }
    
public int indexOf(String str)
public String substring(int start,int end)左闭右开
public int length()
public char charAt(int n)
public void setCharAt(int n,char ch)

总结:

  • 增:append(xxx)
  • 删:delete(int start,int end)
  • 改:setCharAt(int n,char ch)/replace(int start,int end,String str)
  • 查:charAt(int n)
  • 插:insert(int offset,xxx)
  • 长度:length();
  • *遍历:for+charAt() / toStirng()

StringBuilder

StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且提供相关的方法也一样

String、StringBuffer、StringBuilder异同

  • String(JDK1.0):不可变字符序列;底层使用char[];jdk 1.9后用byte[]
  • StringBuffer(JDK1.0):可变字符序列、效率低、线程安全;底层使用char[];jdk 1.9后用byte[]
  • StringBuilder(JDk5.0):可变字符序列、效率高、线程不安全;底层使用char[];jdk 1.9后用byte[]

作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值

多线程用StringBuffer;非多线程用StringBuilder

String str = new String();//char[]value=new char[0]
String str1 = new String("abc");//char[]value=new char[]{'a','b','c'};

StringBuffer sb = new StringBuffer();//char[]value=new char[16];底层创建了一个长度是16的数组
sb.append('a');//value[0]='a';
sb.append('b');//value[1]='b';

StringBuffer sb1 = new StringBuffer("abc");//char[] value=new char["abc".length()+16];
//扩容:若要添加的数据底层数组盛不下了,那就需要扩容底层的数组
对比效率
//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
    buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
    builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
    text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间" + (endTime - startTime));

/*
某次执行结果
StringBuffer的执行时间8
StringBuilder的执行时间5
String的执行时间1577
*/

时间比较

String>StringBuffer>StringBuilder

JDK8之前的日期时间API测试

计算世界时间的主要标准有:

  • UTC(Coordinated Universal Time)
  • GMT(Greenwich Mean Time)
  • CST(Central Standard Time)

java.lang.System

System类提供的public static long currentTimeMillis()用来返回当前时间与1970.1.1 0:0:0之间的毫秒为单位的时间差

  • 此方法用于计算时间差
System.out.println(System.currentTimeMillis());
//时间戳

java.util.Date

表示特定的瞬间,精确到毫秒

  • 构造器
    • Date():使用无参构造器创建构造器创建的对象可以获取本地当前时间。
    • Date(long Date)
  • 常用方法
    • getTime():返回时间戳
    • toString():把此Date对象转换为以下形式的String:dow mon dd hh:mm:ss zzz year 其中dow是一周中的某一天(Sun,Mon,Tue,Wed,Thu,Fir,Sat),zzz是时间标准
    • 其它已过时
/*
java.util.Date
		|--java.sql.Date
*/

//构造器一:Date:创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//笔记时间:Mon Aug 15 18:01:15 CST 2022
System.out.println(date1.getTime());//当前Date对象对应的时间戳

//构造器二:
Date date2 = new Date(date1.getTime());//时间戳
System.out.println(date2.toString());//时间戳格式转换为dow mon day hh:mm:ss zzz year
java.sql.Date
/*
java.util.Date
		|--java.sql.Date
*/

//实例化
java.sql.Date date1=new java.sql.Date(/*时间戳*/);
System.out.println(date1);//year-mon-day

//java.util.Date --> java.sql.Date
//方法一
Date date1=new java.sql.Date(/*时间戳*/);
java.sql.Date date2=(java.sql.Date) date1;
//方法二
Date date3=new Date();
java.sql.Date date4=new java.sql.Date(date3.getTime());

java.text.SimpleDateFormat

Date类的API不易于国际化,大部分被抛弃了,java.text,Simple.DateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类

  • 它允许及进行格式化:日期–》文本、解析:文本–》日期
  • 格式化:
    • SimpleDateFormat():默认的模式和语言环境创建对象
    • public SimpleDateFormat(String pattern):该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用:
    • public String format(Date date):方法格式化事件对象date
  • 解析
    • public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期

时间戳与时间的转换

{
    //时间戳 --》自定义的时间格式
    Long timeStamp = System.currentTimeMillis();  //获取当前时间戳
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//自定义的时间格式
    String sd = sdf.format(new Date(timeStamp));// 时间戳转换成时间
    System.out.println("解析的结果:" + sd);

    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy 年 MM 月 dd 日 HH 时 mm 分 ss 秒");
    String sd2 = sdf2.format(new Date(timeStamp));//时间戳转换为时间
    System.out.println("格式化结果:" + sd2);

    //自定义的时间格式 --》时间戳
    try {
        Long timeTest = sdf2.parse(sd2).getTime();
        System.out.println(timeTest);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
/*
实例化
*/

SimpleDateFormat sdf=new SimpleDateFormat();
Date date=new Date();
String format=sdf.format(date);
System.out.println(date);
System.out.println(format);//year-mon-day 中文的上/下午 hh:mm
/*
格式化
*/

Date date=new Date();
System.out.println(date);

String format=new SimpleDateFormat().format(date);
System.out.println(format);
/*
解析
*/

String str="2022/8/16 下午2:26";
Date date1=new SimpleDAteFormat().parse(str);
System.out.println(date1);
/*
应用
2020-09-08转换为java.sql.Date
*/
String birth="2020-09-08";
SimpleDateFormat sdf=new SimpleDateFormat("yyyy--MM--dd");
Date date=sdf.parse(birth);
java.sql.Date birthDate birthDate=new java.sql.Date(date.getTime());
System.out.println(birthDate);

java.util.Calendar

Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能

  • 获取Calendar实例的方法
    • 使用Callendar.getInstance()方法
    • 调用它的子类GregorianCalendar的构造器(不常用)
  • 一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY、MINUTE、SECOND
    • public void set(int field,int value)
    • public void add(int field,int amount)
    • public final Date getTime()
    • public final void setTime(Date date)

注意:月份是从0开始,周日是1

/*
实例化及常用方法
*/

//调用其静态对象方法getInstance()
Calendar calendar=Calendar.getInstance();//还是其子类GregorianCalendar的对象

//常用方法
//get()
int days=calendar.get(Calendar.DAY_OF_MONTH);//本月的第几天
int days1=calendar.get(Calendar.DAY_OF_YEAR));//今年的第几天

//set()
calendar.set(Calendar.DAY_OF_MONTH,22);//修改时间为22号
//add()
calendar.add(Calendar.DAY_OF_MONTH,-3);
calendar.add(Calendar.DAY_OF_MONTH,3);//本月日+/-天数
//getTime()
Date date=Calendar.getTime();//日历类-->Date()
//setTime()
Date date1=new Date();
Calendar.setTime(date1)//Date-->日历类

JDK8中新日期时间api

旧的api面临的问题有

可变性:像日期和时间这样的类应该是不可变的

偏移性:Date中的年份是从1900开始的,而月份都从0开始

格式化:格式化只对Date有用,Calendar则不行

此外,它们也不是线程安全的;不能处理闰秒等

jdk8之前jdk8对应的
java.util.Date
java.sql.Date
Instant
SimpleDateFormatDateTimeFormatter
CalendarLocalDate、LocalTime、LocalDateTime
api作用
java.time包含值对象的基础包
java.time.chrono提供对不同的日历系统的访问
java.time.format格式化和解析时间和日期
java.tim.temporal包括底层框架和扩展特性
java.time.zone包含失去支持的类

大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概只会用到其中的三分之一

LocalDate、LocalTime、LocalDateTime

LocalDate、LocalTime、LocalDateTime类是其中比较重要的及几个类,它们的实例是不可变的对象,分别表示使用过ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息

  • LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。
  • LocalTime表示一个时间,而不是日期
  • LocalDateTime是用来表示日期和时间的,这是一个常用的类之一

ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就i是公历

方法描述
now()/* now(Zoneld zone)静态方法根据当前时间创建时区的对象
of()静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear()获得月份天数(1-31)/获得年份天数(1-366)
getDayOfWeek()获取星期几(返回一个DayOfWeek枚举值)
getMonthValue()/getYear()获取月份/获取年份
getHour()/getMinute()/getSecond()获得当前对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()
/withMonth()/withyear()
将月份天数、年份天数、月份、年份修改为指定对象并返回
plusDays(),plusWeeks(),
plusMonths(),plusYears(),plusHours
向当先对象添加几天、几周、几个月、几年、几小时
minusMonths()/minusWeeks()/
minusDays()/minusYears()/minusHours
从当前对象减去几月、几周、几天、几年、几小时
//实例化

//now():获取当前的时间日期
LocalDate localDate=LocalDate.now();
LocalTime localTime=LocalTime.now();
LocalDateTime localDateTim=LocalDatetime.now();
System.out.pritnln(localDate);//year-mon-day
System.out.println(localTime);//HH:mm:ss.ms
System.out.println(localDateTime);//yyyy-MM-ddTHH:mm:ss.ms

//of():设置指定的年、月、日、时、分、秒;没有偏移量
LocalDateTime localDateTime=LocalDateTime.of(2022,7,12 13,12,34);
System.out.println(localDateTime);//yyyy-MM-ddTHH:mm:ss.ms;且没有偏移量
//getXxx();获取相关属性
LocalDateTime localDatetime=LocalDateTime.now();
System.out.println(localDateTime.getDayOfMonth());//当月第几天
System.out.println(localDateTime.getDayOfWeek());//本周第几天
System.out.println(localDatetime.getMonth());//获取月份(英语)
System.out.println(localDatetime.getMonthValue());//获取月份(阿拉伯数字)
System.out.println(localDateTime.getMinute());//获取分钟
//withXxxx:设置相关属性
LocalDateTime localDateTime=LocalDateTime.now();
LocalDateTime localDateTime1=localDatetimewithDayOfMonth(22);

Instant

时间线上的一个瞬时点。这可能被用来记录应用程序中的事件时间戳。

  • 在处理时间和日期的时候,我们通常会想到年、月、日、时、分、秒。然而,这只是时间的一个额模型,是面向人类的。第二种通用模型是面向机器的,或者说是连续的。在此模型中,时间线中的一个点表示为一个很大的数,这有利于计算机处理。在UNIX中,这个数从1970年开始,以秒为单位;同样的,在Java中,也是从1970年开始,但以毫秒为单位
  • java.time包通过值类型Instannt提供机器驶入,不提供处理人类意义上的时间单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。感念上讲,他只是简单的表示子1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包时基于纳秒计算的,所以Instant的精度可以达到纳秒级。
  • 1s=103ms=106μs=10^9ns
方法作用
now()静态方法,返回默认UTC时区的Instant类的对象
ofEpochMilli(long epochMilli)静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象
atOffset(ZoneOffset offset)结合即时的偏移来创建一个OffsetDateTime
toEpochMilli()返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00秒00分)起到现在的总秒数

//now():获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant);//

//根据时区添加偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);

//获取对应的时间戳-->Date类的getTime()
long milli=instant.toEpochMilli();
System.out.println(milli);

//ofEpochMilli():通过给定的时间戳,获取Instant实例--> Date(long millis)
Instant instant1=Instant.ofEpochMilli(instant.toEpochMilli);
System.out.println(instant1);

java.time.format.DateTimeFormatter类

格式化或解析日期、时间

类似SimpleDateFormat

该类提供了三种格式化方法

  • 预定义的标准格式。如:

    ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME

  • 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)

  • 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss E”)

方法描述
ofPattern(String pattern)静态方法,返回一个指定字符串格式的DateTimeFormatter
format(TemporalAccessor t)格式化一个日期、时间,返回字符串
parse(CharSwquence text)将指定格式的字符序列解析为一个日期、时间
//自定义的格式。如:ofPattern("yyyy-MM-dd hh:mm:ss E")
DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4=formatter3.format(LocalDateTime.now());
System.out.println(str4);

//解析
TemporalAccessor accessor=formatter3.parse("2022-08-09 11:54:59");
System.out.println(accessor);

Java比较器

java中的对象,正常情况下,只能进行:==或!=。不能使用>或<的。但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。

如何实现?使用两个接口中的任意一个:Comparable和Comparator

java.lang.Comparable

自然排序

  • Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为自然排序
  • 实现Comparable的类必须实现compareTo(Object obj)方法,两个对象即通过compareTo(Object obj)方法,两个对象即通过compareTo(Object obj)方法的返回值来比较大小。如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于相残对象obj,则返回0
  • 实现Comparable接口的对象列表(和数组)可以通过Collections.sort或Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
  • 对于类C的每个e1和e2来说,当且仅当e1.compareTo(e2)==0与e1.equals(e2)具有相同的boolean值时,类C的自然排序才叫做与equals一致。建议(虽然不是必须的)最好使自然排序与equals一致
String[] arr = new String[]{"AA", "CC", "BB", "DD", "ZZ", "MM", "KK"};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));//[AA, BB, CC, DD, KK, MM, ZZ]
//因为String、包装类等实现类Comparable接口,重写了comparaTo()方法,给出了比较两个对象大小的方式

重写compareTo()规则

  • 如果当前对象this大于形参对象obj,则返回正整数;
  • 如果当前对象this小于形参对象obj,则返回负整数;
  • 如果当前对象this等于相残对象obj,则返回0
自定义类实现Comaprable自然排序

按照价格从低到高排序,再按照产品名称从低到高排序

class test{
    public static void main(String[] args) {
        Goods[] arr = new Goods[4];
        arr[0] = new Goods("water", 4);
        arr[1] = new Goods("phone", 4999);
        arr[2] = new Goods("food", 12);
        arr[3] = new Goods("computer", 9999);
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}
class Goods implements Comparable {//实现Comparable接口
    private String name;//商品名
    private double price;//价格

    public Goods() {
    }

    public Goods(String name, double price) {
        this.name = name;
        this.price = price;
    }

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

    public void setPrice(double price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return "Goods{" + "name='" + name + '\'' + ",price=" + price + "}";
    }

    //指名商品比较大小的方式:按照价格从低到高排序,再按照产品名称从低到高排序
    @Override
    public int compareTo(Object o) {
        if (o instanceof Goods) {
            Goods goods = (Goods) o;
            return (this.price > goods.price) ? 1 : (this.price < goods.price) ? -1 : this.name.compareTo(goods.name);//name是String,可以调用现成的compareTo()
        }
        throw new RuntimeException("传入的数据类型不一致");
    }
}

java.util.Comparator

定制排序

  • **当元素的类型没有实现java.lang.Comparable接口而有不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,纳秒可以考虑使用Comparator的对象来排序,**强调多个对象进行整体排序的比较
  • 重写compare(Object 01,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回o则表示相等;返回负整数,表示o1小于o2
  • 可以将Comparator传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制
  • 还可以使用Comparator来控制某些数据接口(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
应用
//自定义排序:字符串从大到小排列

String[] arr = new String[]{"a", "c", "z", "g", "b"};
Arrays.sort(arr, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        if (o1 instanceof String && o2 instanceof String) {
            String s1 = (String) o1;
            String s2 = (String) o2;
            return -s1.compareTo(s2);
        }
        throw new RuntimeException("输入的数据类型不一致");
    }
});
System.out.println(Arrays.toString(arr));
//先产品名从低到高,再价格从高到低
//由Comparable的例子修改的来,详细的代码请见Comparator
Goods[] arr = new Goods[4];
arr[0] = new Goods("cater", 4);
arr[1] = new Goods("phone", 4999);
arr[2] = new Goods("food", 12);
arr[3] = new Goods("computer", 9999);
Arrays.sort(arr,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.out.println(Arrays.toString(arr));

对比

Comparable:

  1. 对象所属的类,需要实现Comparable接口,并重写comparable方法
  2. 实现Comparable接口的类在任何位置都能比较大小

Comparator:

  1. 临时性,可根据需要临时创建

System类

System类代表系统,系统级的很多属性和控制方法都放置再该类的内部。该类位于java.lang包

  • 由于该类的构造其实private的,所以无法创建该类的对象,也就是无法实例化类。其内部成员变量和成员方法都是static的,所以也可以很方便的进行调用

  • 成员变量

    • System类包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(红色字体)(显示器)
  • 成员方法

    • native long currentTimeMillis():

      该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970-01-01 00:00:00之差的毫秒数

    • void exit(long status):

      该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能

    • void gc():

      该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现即系统执行时的情况

    • String getProperty(String key):

      该方法的作用是获得系统中属性名为keg的属性对应的值。系统中常见的属性名以及属性的作用如下所示

属性名说明
java.versionjava运行时环境版本
java.homejava安装目录
os.name操作系统名称
os.version操作系统版本
user.name用户账户名称
user.home用户的主目录
user.dir用户的当前工作目录
System.out.println(System.currentTimeMillis());
System.exit();
System.out.println("java.version:"+System.getProperty("java.version"));
System.out.println("java.home:"+System.getProperty("java.home"));
System.out.println("os.name:"+System.getProperty("os.name"));
System.out.println("os.version:"+System.getProperty("os.version"));
System.out.println("user.name:"+System.getProperty("user.name"));
System.out.println("user.home:"+System.getProperty("user.home"));
System.out.println("user.dir:"+System.getProperty("user.dir"));

Math类

java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型

方法作用
abs()绝对值
acos(),asin(),atan(),cos(),sin(),tan()三角函数
sqrt()平方根
pow(double a,double b)a的b次幂
log()自然对数
exp()e为底指数
max(double a,double b)
min(double a,double b)
random()0.0到1.0的随机数
long round(double a)double型数据a转换为long型(四舍五入,精度丢失)
toDegrees(double angrad)弧度–>角度
toRadians(double angdeg)角度–>弧度

BigInteger与BigDecimal

BigInteger

Integer类作为int的包装类,能存储的最大整型值为231-1,Long类也是有限的,最大为263-1如果要表示再打的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了

  • java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger提供所有Java的基本整数操作符的对应物,并提供java.lang.Math的所有相关方法。另外,BigInteger还提供以下运算:模算术、GCD运算、指数测试、素数生成、位操作以及一些其他操作
  • 构造器

    • BigInteger(String val):根据字符串构建BigInteger对象
  • 常用方法

    方法作用
    public BigInteger abs()返回BigInteger的绝对值的BigInteger
    BigInteger add(BigInteger val)返回值为**(this+val)**的BigInteger
    BigInteger subtract(BigInteger va)返回值为**(this-val)**的BigInteger
    BigInteger multiply(BigInteger val)返回其值为**(this*val)**的BigInteger
    BigInteger divide(BigInteger val)返回其值为**(this/val)**的BigInteger
    BigInteger remainder(BigInteger val)返回其值为 **(this%val)**的BigInteger
    BigInteger[] divideAndRemainder(BigInteger val)返回包含BigInteger [] { this/val , this%val }
    BigInteger pow(int exponent)返回其值为**(this^exponet)**的BigInteger

BigDecimal

一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到java.math.BigDecimal类

  • BigDecimal类支持不可变、任意精度的有符号十进制点数
  • 构造器

    • public BigDecimal(double val)
    • public BigDecimal(String val)
  • 常用方法

    方法作用
    public BigDecimal add(BigDecimal augend)return this+augend;
    public BigDecimal subtract(BigDecimal subtrahend)return this-subtrahend;
    public BigDecimal multiply(BigDecimal multiplicand)return this*multiplicand;
    public BigDecimal divide(BigDecimal divisor,int cale,int roundingMode)divisor/cale; coundingMode是运算模式

枚举类和注解

枚举类

  • 类的对象只有有限个,确定的
    • 星期:Monday、……Sunday
    • 性别:Man、Woman
    • 季节:Spring……Winter
    • 线程状态:创建、就绪、运行、阻塞、死亡
  • 当需要定义一组常量时,强烈建议使用枚举类
  1. 枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
  2. 当需要定义一组常量时,强烈建议使用枚举类
  3. 如果枚举类只有一个对象,则可以作为单例模式的实现方式

使用

  • 实现
    • jdk1.5之前需要自定义枚举类
    • jdk1.5新增的enum关键字用于定义枚举类
  • 若枚举只有一个对象,则可以作为一种单例模式的实现方式
  • 枚举类的属性
    • 枚举类对象的属性不应允许被改动,所以应该使用private final修饰
    • 枚举类的使用private final修饰的属性应该在构造器中为其赋值
  • 若枚举类显式的定义了带参数的构造器,则在列出枚举值时也必须对应的传入参数

自定义枚举类

jdk5.0之前

class SeansonTest {
    public static void main(String[] args) {
        Season spring = Season.SPRING;
        System.out.println(spring);
    }
}

class Season {
    //声明Season对象的属性
    private final String seasonName;
    private final String seasonDesc;

    //私有化类的构造器,并给对象属性赋值
    private Season(String seasonName, String seasonDesc) {
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //提供当前枚举类的多个对象public static final
    public static final Season SPRING = new Season("春天", "春暖花开");
    public static final Season SUMMER = new Season("夏天", "夏日炎炎");
    public static final Season AUTUMN = new Season("秋天", "秋高气爽");
    public static final Season WINTER = new Season("冬天", "冰天雪地");

    //其他诉求对象的属性
    public String getSeaSonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    @Override
    public String toString() {
        return "Season{" + "seasonName='" + seasonName + '\'' + ",seasonDesc='" + seasonDesc + '\'' + '}';
    }
}

【推荐】使用enum关键字定义枚举类

jdk5.0新增的关键字

定义的枚举类默认继承于java.lang.Enum类

class SeansonTest {
    public static void main(String[] args) {
        Season summer = Season.SUMMER;
        System.out.println(summer);
    }
}

enum Season {

    //提供当前枚举类的多个对象,多个对象之间用“,”隔开,末尾以";"结束
    SPRING("春天", "春暖花开"),
    SUMMER("夏天", "夏日炎炎"),
    AUTUMN("秋天", "秋高气爽"),
    WINTER("冬天", "冰天雪地");

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

    //私有化类的构造器,并给对象属性赋值
    private Season(String seasonName, String seasonDesc) {
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //其他诉求对象的属性
    public String getSeaSonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    //可以不重写toString,默认返回seasonName
    @Override
    public String toString() {
        return "Season{" + "seasonName='" + seasonName + '\'' + ",seasonDesc='" + seasonDesc + '\'' + '}';
    }
}
常用方法
方法作用
values()返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值
valueOf(String str)可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException
to’string()返回当前枚举类对象常量的名称
class SeansonTest {
    public static void main(String[] args) {
        Season summer = Season.SUMMER;
        //toString():
        System.out.println(summer);

        //values();
        Season[] values = Season.values();
        System.out.println(Arrays.toString(values));

        Thread.State[] values1=Thread.State.values();
        for(int i=0;i<values1.length;i++){
            System.out.println(values1[i]);
        }

        //valueOf(String objName):返回枚举类中对象名是objName的对象
        //如果没有objName的对象,则报IllegalArgumentException
        Season winter=Season.valueOf("WINTER");
        System.out.println(winter);
    }
}

enum Season {

    //提供当前枚举类的多个对象,多个对象之间用“,”隔开,末尾以";"结束
    SPRING("春天", "春暖花开"),
    SUMMER("夏天", "夏日炎炎"),
    AUTUMN("秋天", "秋高气爽"),
    WINTER("冬天", "冰天雪地");

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

    //私有化类的构造器,并给对象属性赋值
    private Season(String seasonName, String seasonDesc) {
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //其他诉求对象的属性
    public String getSeaSonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    //可以不重写toString,默认返回seasonName
    @Override
    public String toString() {
        return "Season{" + "seasonName='" + seasonName + '\'' + ",seasonDesc='" + seasonDesc + '\'' + '}';
    }
}
使用enum关键字定义的枚举类实现接口的情况
  • 情况一:实现接口,在enum类中实现抽象方法
  • 情况二:让枚举类的对象分别实现接口中的抽象方法

class SeansonTest {
    public static void main(String[] args) {
        Season summer = Season.SUMMER;
        //toString():
        System.out.println(summer);

        //values();
        Season[] values = Season.values();
        System.out.println(Arrays.toString(values));

        Thread.State[] values1=Thread.State.values();
        for(int i=0;i<values1.length;i++){
            System.out.println(values1[i]);
        }

        //valueOf(String objName):返回枚举类中对象名是objName的对象
        //如果没有objName的对象,则报IllegalArgumentException
        Season winter=Season.valueOf("WINTER");
        System.out.println(winter);
    }
}

interface Info{
    void show();
}

enum Season implements Info{

    //提供当前枚举类的多个对象,多个对象之间用“,”隔开,末尾以";"结束
    SPRING("春天", "春暖花开")
        
    //单独设置每个对象的show()
//            {
//        @Override
//        poublic void show(){
//            System.out.println("春天")
//        }
//    }
    ,
    SUMMER("夏天", "夏日炎炎"),
    AUTUMN("秋天", "秋高气爽"),
    WINTER("冬天", "冰天雪地");

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

    //私有化类的构造器,并给对象属性赋值
    private Season(String seasonName, String seasonDesc) {
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //其他诉求对象的属性
    public String getSeaSonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    //可以不重写toString,默认返回seasonName
    @Override
    public String toString() {
        return "Season{" + "seasonName='" + seasonName + '\'' + ",seasonDesc='" + seasonDesc + '\'' + '}';
    }

    @Override
    public void show(){
        System.out.println("这是一个季节");
    }
}

注解

Annotation

  • 从JDK5.0开始,Java增加了对元数据(MetaData)的支持,也就是Annotation(注解)
  • Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处。通过使用Annotaition,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或进行部署
  • Annotation可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数局部变量的声明,这些信息被保存在Annotation的“name=value”对中
  • 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在javaEE/android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等
  • 未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说:框架=注解+反射+设计模式

常见的Annotation示例

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

jdk 5.0新增

文档相关

@author 标明开发该类模块的作者,多个作者之间使用,分割

@version 表明该类模块的版本

@see 参考转向,也就是相关主题

@param 对方法中某参数的说明,如果没有参数就不能写

@return 对方法返回值的说明,如果方法的返回值是void就不能写

@exception 对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出的异常就不能写


@param @return @exception 这三个标记都只用于方法

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

@return的格式要求:@return 返回值类型 返回值说明

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

@param @exception可以并列多个


示例三: 跟踪代码依赖性,实现替代配置文件功能

Servet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行servlet的部署

@WebServlet("/login")
public class Loginservlet extends HttpServlet{
    private void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException , IOException{}
    protected void doPost(HttpServletRequest request,HttpServletResponse reponse) throws ServletException, IOException{
        doGet(reques, response);
    }
}
<!--
@webServlet("/login")
替换了以下内容
-->
<servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/login</url-pattern>
</servlet-mapping>

Spring框架中关于“事物”的管理

@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)
public void buyBook(String username,String isbn){
    //1.查询书的单价
    int price = bookShopDao.findBookPriceBylsbn(isbn);
    //2.更新库存
    bookShopDao.updateBookStock(isbn);
    //3.更新用户的余额
    bookShopDao.updateUserAccount(username,price);
}
<!--配置实物属性-->
<tx:advice transaction-manager="dataSoutceTransactionManager" id="txAdvice">
    <tx:attributes>
        <!--配置每个方法使用的事务属性-->
        <tx:method name="buyBook" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" read-only="false" timeout="3"/>
    </tx:attributes>
</tx:advice>

另外,Junit单元测试中也有大量的注解使用。简单罗列到下面,这里不再赘述

@Test:标记在非静态的测试方法上。只有标记@Test的方法才能被作为一个测试方法单独测试。一个类中可以有多个@Test标记的方法。运行时如果只想运行其中一个@Test标记的方法,那么选择这个方法名,然后单独运行,否则整个类的所有标记了@Test的方法都会被执行

  • Test(timeout=1000):设置超时时间,如果测试时间超过了你定义的timeout,测试失败
  • Test(expected):声明出会发生的异常,比如@Test(epected= Exception.class)

举例

/**
 *@author	lf
 *@version	2.0
 *@see	Math.java
 */
public class JavadocTest{
    /**
     *程序的主方法,程序的入口
     *@param args string	命令行参数
     */
    public static void main(String[] args ){
    }
    /**
     *求圆面积的方法
     *@param radius double 半径值
     *@return double 圆的面积
     */
    public static double getArea(double radius){
        return Math.PI*radius*radius;
    }
}
编译进行格式检查(JDK内置的三个基本注解)

@Override: 限定重写父类方法,该注解只能用于fangfa

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

@SuppressWarnings: 抑制编译器警告

了解

@Test(timeOut=1000):设置超时时间,如果测试时间超过了你定义的timeOut,测试石板

@Teset(expected):申明出会发生的异常,比如@Test(Expected=Exception.class)

@BeforeClass:标记在静态方法上,因为这个方法只执行一次。在类初始化时执行

@AfterClass:标记在静态方法上,因为这个方法只执行一次。在所有方法完成后执行

@Before:标记在非静态方法上,在@Test方法前面执行。而且是在每个@Test方法前面都执行

@After:标记在非静态方法上,在@Test方法后面执行。而且是在每一个@Test方法后面都执行

@Ignore:标记在本次不参与测试时的方法上。这个注解的含义就是“某些方法尚未完成,暂不参与此次测试时“。

@BeforeClass @AfterClass @Before @After @Ignore都是配合@Test它使用的,单独使用没有意义

import org.junit.After;

import org.junit.AfterClass;

import org.junit.Before;

import org.junit.BeforeClass;

import org.junit.Test;

自定义

  • 定义新的Annotation类型使用**@interface**关键字
  • 自定义注解自动继承了**java.lang.annotation.Annotation接口****
  • Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们成为配置参数。类型只能是八种基本数据类型、String类型、enum类型、Annotation类型、以上所有类型的数组
  • 可以在定义Annotation的成员变量时为其只当初始值,指定成员变量的初始值可以使用default关键字
  • 如果只有一个参数成员,建议使用参数名为value
  • 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名=参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
  • 没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元数据Annotation

自定义注解必须配上注解的信息处理流程才有意义(使用反射)

自定义注解通常会知名两个元注解:Retention、Target

参照``@SupperessWarinnings`定义

  • 定义新的Annotation类型使用@interface关键字
  • 自定义注解自动继承来java.lang.annotation.Annotation接口
  • Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值定义类该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型以上所有类型的数组
  • 可以在定义Annotation的成员变量时为其指定初始值,只当成员变量的初始值可使用default关键字
  • 如果只有一个参数成员,建议使用参数名为value
  • 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名=参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
  • 没有成员定义的Annotation称为标记;包含成员变量的Annotaiton称为元数据Annontation
  • 注意:自定义注解必须配上注解的信息处理流程(使用反射)才有意义
@Retention(RetentionPolicy.RUNTIME)//可以通过反射获取
@Tatget({TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})//只能修饰:(类、接口、枚举类)、属性、方法、形参、构造器、局部变量
public @interface MyAnnotation{
    //内部定义成员,通常使用value表示;
    //可以指定成员的默认值,使用default表示
    //如果自定义注解没有成员,表示一个标识作用,如@Override
    String value() default "hi"; 
}
//使用
@MyAnnotation(value="hello")
//因为只有一个成员也可以写成@MyAnnotation("hello")
//如果注解有成员,在使用注解时,需要指明成员变量的值。有默认值也可以不指定
public class Person{
    private Stirng name;
    private int age;
    public Person(){
        
    }
    @MyAnnotation
    public person(String name,int age){
        this.name=name;
        this.age=age;
    }
    public void walk(){System.out.println("没事走两步");}
    public void eat(){System.out.println("吃嘛嘛香");}
}

JDK中的元注解

元注解:对现有的注解进行解释说明的注解

JDK5.0提供了4个标准的meta-annotation类型,分别是:

  • Retention

    只能用于修饰一个Annotation定义,用于指定该Annotation的生命周期,@Rentention包含一个RetentionPolicy类型的成员变量,使用@Rentention时必须为该value成员变量指定值:

    • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
    • RetentionPolicy.CLASS:在class文件中邮箱(即class保留),当运行Java程序时,JVM不会保留注释。这是默认值
    • RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当与性能JAVA程序时,JVM会保留注释。程序可以通过反射获取该注释。
  • Target

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

      取值
      CONSTARUCTOR用于描述构造器
      PACKGE用于描述包
      FIELD用于描述域
      PARAMETER用于描述参数
      LOCAL_VARIABLE用于描述局部变量
      TYPE用于描述类、接口(包括注解类型)或enum声明
      METHOD用于描述方法
  • Documented

    用于指定被该元Annotaition修饰的Annotation类将被Javadoc工具提取成文档。默认情况下,Javadoc是不包括注解的

    定义为Documented的注解必须设置Retention值为RUNTIME

  • Inherited

    ​ 被它修饰的Annotaion具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解

    ​ 比如:如果吧标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解

    实际应用较少

元注解:对现有的注解进行解释说明的注解

  • @Retention:指定所修饰的annotation的生命周期:SOURCE\CLASS(默认行为)\RUNTIME(只有声明为RUNTIME生命周期的注解,才能通过反射获取。)
  • @Target:用于指定被修饰Annotation能修饰哪些程序元素:TYPE(类、接口、枚举类)\FIELD(属性)\METHOD(方法)\PARAMETER(形参)\CONSTRUCTOR(构造器)\LOCAL_VARIABLE(局部变量)

以下频率较低

  • @Documented:标识所修饰的注解在被javadoc解析时,保留下来
    • 定义为Documented的注解必须设置Retention值为RUNTIME
  • @Inherited:被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解
    • 比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解

重复注解(jdk8新特性)

  1. 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
  2. MyAnnotation的Target和Retention和MyAnnotations相同
@Repeatable(MyAnnotations.class)
//加上此注解可重复
public @interface MyAnnotations{
    MyAnnotation[] value();
}

//jdk8之前
//@MyAnnotation({@MyAnnotation(value="hi"),@MyAnnotation(value="hello")})
//jdk8
@MyAnnotation(value="hi")
@MyAnnotation(value="hello")
class Person{
    
}

类型注解(新)

  • JDK1.8之后关于元注解@Target的参数类型ElementType枚举值多了两个:TYPE_PARMETER,TYPE_USE

  • 在Java8之前,注解只能是在声明的地方使用,Java8开始,注解可以应用在任何地方。

    • ElementType.TYPE_PARAMETER表示该注解能写在类型变量的声明语句中(如:泛型声明)
    • ElementType.TYPE_USE表示该注解能卸载使用类型的任何语句中。
    public class TestTypeDefine<@TypeDefine() U>{
        private U u;
        public <@TypeDefine() T> void test(T t){
            
        }
    }
    @Target({ElementType.TYPE_PARAMETER})
    @interface TypeDefine{
    }
    
    class Generic<@MyAnnotation T> throws @MyAnnotation RuntimeException{
        public void show(){
            ArrayList<@MyAnnotation String> list = new ArrayList<>();
            int num = (@MyAnnotation int) 10L;
        }
    }
    @Target({TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
    @interface MyAnnotation{
        String value defaul="hi";
    }
    

Java集合

Java集合框架的概述

集合、数组都是对多个数据及逆行存储操作的结构,简称Java容器。(此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储{.txt,.jpg,.avi,数据库中};

  • 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端Java集合就像一种容器,可以动态地把多个对象引用放入容器中。

数组在内存存储方面的特点

  • 数组初始化以后,长度就确定了
  • 数组声明的类型,就决定了

数组在存储数据方面的弊端

  • 数组初始化以后长度就不可变了,不便于扩展
  • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数
  • 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
  • 数组存储的数据是有序的、可重复的。–>存储数据的特点单一

集合存储方面的优点:解决数组存储数据方面的弊端

Java集合类可以用于存储数量不多的多个对象,还可用于保存具有映射关系的关联数组

Java集合可分为Collection和Map两种体系

  • Collection接口:单列数据,定义了存取一组对象的方法的集合
    • List:元素有序、可重复的集合
    • Set:元素无序、不可重复的集合
  • Map接口,双列数据,保存具有映射关系“key-value对”的集合

集合框架

|—Colection接口:单列集合,用来存储一个一个的对象
|—List接口:存储有序的、可重复的数据 --》动态数组
|—ArrayList:作为List接口的最常用实现类;线程不安全、效率高;底层使用Object[] elementDate存储
|—LinkedList:对于频繁的插入、删除操作性能优于ArrraysLis;底层使用双向列表
|—Vector:最为List接口的古老实现类(DK1.0);线程安全,效率低;底层使用Object[] 存储
|—Set接口:存储无序的、不可重复的数据 --》高中讲的“集合”

​ |—HashSet:Set接口的主要实现类;线程不安全;可以存储null值

​ |—LinkedHashSet:最为HashSet子类;遍历内部数据时,可以按照添加的顺序遍历对于频繁的的遍历操作,LinkedHashSet效率高于HashSet

​ |—TreeSet:可以按照添加对象的指定属性,进行排序

​ |—Map接口:双列集合,用来存储一对(key-value)一对的数据 --》高中的函数:y=f(x)|—Map存储双列数据,存储key-value 类似于高中的函数:y=f(x)

​ |—HashMap:作为Map的主要实现类,线程不安全,效率高;存储null的key和value

​ |LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有的HashMsp底层结构基础上,添加了一对指针,指向前一个和后一个元素。

​ 对于频繁的遍历操作,此类执行效率高于HashMap。

​ |—TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序

​ 底层使用红黑树

​ |—Hashtable:作为Map古老的实习类;线程安全,效率低;不能存储null的key和value

​ |—Properties:常用来处理配置文件,key和value都是String类型

单列集合

Collection

常用方法的使用
  • 添加元素(或集合)
    • add(Object obj):将元素e添加到集合coll中
    • addAll(Colection coll):将另一个coll的元素添加到当前集合中,coll不受影响
  • 获取有效元素得个数
    • int size()
  • 清空集合
    • void clear()
  • 是否为空集合
    • boolean isEmpty()
  • 是否包含某个元素(或集合)
    • boolean contains(Object obj):是通过元素得equals方法来判断是否是同一个对象
    • booblean containsAll(Colections c):也是调用元素得equals方法来比较的。拿两个集合得元素挨个比较
  • 删除
    • boolean remove(Object obj):通过元素的equals方法判断是否是要删除的那个元素。删除成功则返回ture;没有该元素则返回false。要是有两个相同的话,只移除一个
    • boolean reamoveAll(Collection coll):取当前集合的差集。从当前集合中移除cool1中所有的元素
  • 取两个集合的交集
    • boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
  • 集合是否相等
    • boolean equals(Object obj)
  • 集合转为对象数组
    • Object[] toArray()
  • 对象转为集合
    • List<T> Arrays.asList():
      • List<String> arr=Arrays.asList(new String[]{“AA”,”BB”,”CC”});
      • List arr=Arrays.asList(123,456);
  • 获取集合对象的哈希值
    • hashCode()
Collection coll = new ArrayList();
//add(Object e)
coll.add("AA");
coll.add(123);
coll.add(new Date());

//size()
System.out.println(coll.size());//元素个数

//addAll(collection c)
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll1.addAll(coll);

//clear()	可清楚单个元素,未指定时则清空集合
coll.clear();

//isEmpty()	判断是否为空
System.out.println(coll.isEmpty());//true
System.out.println(coll1.isEmpty());//flase
遍历,使用迭代器Iterator接口
  • iterator():返回迭代器对象,用于集合遍历

  • Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。

  • Iterator仅用于遍历集合。集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认的游标都在集合的第一个元素之前。

  • 方法作用
    next()返回迭代器下一个元素
    hasNext()如果迭代器还有更多元素,则返回true
    remove()可以再遍历时,姗迟集合中的元素。此方法不同于集合直接调用remove()
    未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove都会报IllegalStateException.
Collection coll=new ArrayList();
coll.add("AA");
coll.add(123);
coll.add(new Date());

//方式一:不推荐
Iterator interator=coll.iterator();
for(int i=0;i<coll.size();i++){
    System.out.println(interator.next());
}
//NoSuchElementException:没有这个元素
try{System.out.println(interator.next());}catch(Exception e){e.printStackTrace();}

//方式二:推荐
//hasNext():判断是否还有下一个元素
while(interator.hasNext()){
    //next();指针下移,将下移以后集合位置上的元素返回
    System.out.println(coll.iterator().next());
}

//bug
//每次使用的迭代器不同
//每次调用iterator()方法都得到一个全新的迭代器对象
while(coll.iterator().hasNext()){
    System.out.println(coll.iterator().next());
}

//remove
Iterator iterator=coll.iterator();
while(iterator.hasNext()){
    Object obj=iterator.next();
    if("AAA".equals(obj)){//如果有"AAA"的对象
        iterator.remove();//删除
    }
}
foreach循环
  • Java5.0提供了foreach循环,用于遍历Collection和数组
  • 遍历操作不需获取Collection或数组长度,无需使用索引访问元素
  • 遍历集合的底层调用Iterator完成操作
  • foreach还可以用来遍历数组
for(/*集合元素的类型*/ /*局部变量*/:/*要遍历的结构名称*/){
    System.out.println(/*局部变量*/.getName());
}
Collection coll=new ArrayList();
coll.add(123);coll.add("AAA");coll.add(new Date());
//内部仍然调用迭代器
for(Object obj:coll){
    System.out.println(obj);
}

//数组也适用
int [] arr=new int []{1,2,3,45,5};
for(int i:arr){
    System.out.println(i);
}
List接口
  • 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
  • List集合类中元素有序、且可重复,集合中每个元素都有其对应的顺序索引。
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
  • JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。
ArrayList、LinkedList、Vector三者的异同?
  • 同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
  • 异:
    • ArrayList:作为List接口的最常用实现类;线程不安全、效率高;底层使用Object[] elementDate存储
    • LinkedList:对于频繁的插入、删除操作性能优于ArrraysLis;底层使用双向列表
    • Vector:最为List接口的古老实现类(DK1.0);程安全,效率低;底层使用Object[] 存储
源码分析

详情在B站搜索“尚硅谷 java”查看

//ArrayList

//jdk7
ArrayList list=new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(1);//elementDate[0]=new Integer(1);
//....
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容默认情况下,扩容为原来的1.5倍,同时需要将原有数组中的数据复制到新的数组中
//结论:建议开发中使用带参的构造器:ArrayList list=new ArrayList(int capacity);

//jdk8
ArrayList list=new ArrayList();//底层Object[] elementData初始化为{},并没有创建长度为10的数组
list.add(1);//第一次调用add()时,底层才创建了长度为10的数组,并将数据1添加到elementDate[0]
//后续添加和扩容与jdk7无异

//jdk7中的ArrayList的创建类似于单例的饿汉式,而jdk8中的ArrayList的对对象的创建类似于单例的懒汉式,延迟了数组的创建,节省了内存
//LikedList
LinkedList list=new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(1);//将Node封装到Node中,创建了Node对象
		/**
 		 *其中Node定义为:体现了LinkedList的双向链表的说法
 		 *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;
 		 *	}
 		 *}
//Vector
//jdk7和jdk8中通过Vector()构造器,底层都创建了长度为10的数组
//扩容方面,默认扩容为原数组的2倍
常用方法

增:add(Object obj)

删:remove(int index) / remove(Object obj)

改:set(int index,Object ele)

查:get(int index)

插:add(int index,Object ele)

长度:size()

遍历:1. Iterator迭代器

   2. foreach循环
   2. for循环
方法用途
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,Objet ele)设置指定index位置的元素为ele
List subList(int fromIndex,int toIndex)返回fromIndex到toIndex位置的子集合
//遍历
//初始化
ArrayList list=new ArrayList();
list.add(123);list.add(AA);list.add(new Date());

//迭代器
Iterator iterator=list.iterator();
while(iterator.hasNext()){
    System.out.pritnln(iterator.next());
}

//foreach
for(Object obj:list){
    System.out.println(obj);
}

//for
for(int i=0;i<list.size();i++){
    System.out.println(list.get(i));
}
//删除
List list = new ArrayList();
list.add(1);
list.add(3);
list.add("a");
System.out.println(list);   //[1, 3, a]
list.remove(1);      	 //删除index为1的元素
System.out.println(list);   //[1, a]
list.remove(new String("3"));//删除实际数据为3的元素
System.out.println(list);   //[1, a]

Set

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

  • Set集合不允许包含相同的元素,如果试吧两个相同的元素加入同一个Set集合中,则添加操作失败

  • Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法

  • Set常用接口有:HashSet、LinkedHashSet、TreeSet

    |—Collection接口;单列集合,用来存储一个一个的对象

    ​ |—Set接口:存储无序的、不可重复的数据

    ​ |—HashSet:Set接口的主要实现类;线程不安全;可以存储null值

    ​ |—LinkedHashSet:最为HashSet子类;遍历内部数据时,可以按照添加的顺序遍历

    ​ 对于频繁的的遍历操作,LinkedHashSet效率高于HashSet

    ​ |—TreeSet:可以按照添加对象的指定属性,进行排序

Set接口中没有额外定义新的方法,使用的都是Colection中声明过的方法

要求:向Set中添加得数据,其所在得类一定要重写hashCode()和equals()

要求:重写得hashCode()和equals()尽可能保持一致性(相等得对象必须具有相等得散列码)

尽量使用软件自动生成equals()和hashCode()

  1. 无序性:不等于随机性;例如HashSet中存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的hash值确定的。
  2. 不可重复性:保证添加的元素按照equals()判断,不能返回true,即相同的元素只能添加一个。
Set set=new HashSet();
set.add(123);set.add("AA");set.add(new Date());

Iterator iterator=set.iterator();
whlile(iterator.hasNaxt()){
    System.out.println(iterator.Next());
}
添加元素的过程
  • HashSet

    像HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的hash值,此hash值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:

    如果此位置没有元素,则元素a添加成功

    如果此位置上有元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值

    ​ 如果hash值不相同,则元素a添加成功

    ​ 如果hash值相同,进而需要调用元素a所在类的equls()方法:

    ​ equals()返回true,元素a添加失败

    ​ equals()返回false,则元素a添加成功

    对于添加成功的第二种和第三种情况而言:

    元素a与已经存在指定索引位置上数据以链表的方式存储

    jdk7中:元素a放与数组中,指向原来的元素

    jdk8中:原来的元素在数组中,指向元素a

    总结:7上8下

  • HashSet底层:数组+链表结构

为什么用Eclipse/IDEA工具里hashCode()重写hashCode方法,有31这个数字

  • 选择系数的时候要选择尽量大的系数,因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也就会提高。(减少冲突)
  • 并且31只占用5bits,相乘造成数据溢出的概率较小。
  • 31可以由i*31==(i<<5)-1表示,现在很多虚拟机里面都有相关优化。(提高算法效率)
  • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,纳秒最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)

​ LinkedHashSet

//LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了一对两个引用,记录此数据前一个和后一个数据
//优点:对于频繁的的遍历操作,LinkedHashSet效率高于HashSet

TreeSet

  • 向TreeSet中添加的数据,要求是相同类的对象。

排序

//自然排序(实现Comparable接口)
//自然排序中,比较两个对象是否相同的标准为:compareTo()返回,不再是equals()
TreeSet set=new ThreeSet();
set.add(123);set.add(-1);set.add(0);
Iterator iterator=set.iterator();
while(iterator.hasNext()){
    System.out.print(iterator.next());
}
//-1 0 123
//从小到大的顺序输出
//String类型,也是从小到大的顺序输出

//按照姓名从大到小排序
class User{//自定义类(user)中
    @Override
    public int compareTo(Object o){
        if(o instanceof User){
            User user=(User) o;
            int compare = this.name.compareTo(user.name);
            if(compare!=0){
                return compare;
            }else{
                return Integer.compare (this.age,user.age);
            }
        }else{
            throw new RuntimeException("输入的类型不一致");
        }
    }
}
//定制排序
//比较两个对象是否相同的标准为:compare()返回0,不再是equals().
test(){
    Comparator com=new Comparator(){
        @Override
        public int compare(Object o1,Object o2){
           if(o1 instanceof User && o2 instanceof User){
               User u1=(User)o1;
               User u2=(User)u2;
               return Integer.compare(u1.getage(),u2.getAge);
           }else{
               throw new RuntimException("输入的数据类型不匹配");
           }
        }
    }
    TreeSet set=new TreeSet(com);
    set.add(new User("Tom",12));
    set.add(new User("Jerry",13);
	set.add(new User("Mike",15));         
}

Map

|—Map存储双列数据,存储key-value 类似于高中的函数:y=f(x)

​ |—HashMap:作为Map的主要实现类,线程不安全,效率高;存储null的key和value

​ |LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有的HashMsp底层结构基础上,添加了一对指针,指向前一个和后一个元素。

​ 对于频繁的遍历操作,此类执行效率高于HashMap。

​ |—TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序

​ 底层使用红黑树

​ |—Hashtable:作为Map古老的实习类;线程安全,效率低;不能存储null的key和value

​ |—Properties:常用来处理配置文件,key和value都是String类型

HashMap的底层:数组+链表 (jdk7及以前)

​ 数组+链表+红黑树 (jdk8)

方法作用
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集合
Map map=new HashMap();
map.put("AA",123);
map.put(123,456);
map.put("aa",789);

//遍历key
Set set=map.keySet();
for(Object obj:set){
    System.out.println(obj);
}

//遍历value
Collection values = map.values();
for(Object obj:values){
    System.out.println(obj);
}

//遍历所有的key-value
Set set1 = map.entrySet();
for(Object obj:set1){
    System.out.println(obj);
}

存储结构

Map中的key是无序的、不可重复的,使用Set存储所有的key --》key所在的类要重写equals()和hashCode() (以HashMap为例)

Map中的value:无序的、可重复的,使用Collection存储所有的value --》value所在的类要重写equals()

  • 一个键值对:key-value构成了一个Entry对象
  • Map中的entry:无序的、不可重复的,使用Set存储所有的entry

常用方法

  • 添加:put(Object key,Object value)
  • 删除:remove(Object key)
  • 修改:put(Object key,Object value)
  • 查询:get(Object key)
  • 长度:size()
  • 遍历:keySet()/values()/entrySet()

Collections工具类

  1. 操作Collection和Map的工具类

  2. 常用方法
    在这里插入图片描述

在这里插入图片描述

说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、HashMap转换为线程的。使用synchronizedList(List list)和synchronizedMap(Map map)

泛型

泛型

就是允许在定义类、接口时通过一个标识标识类中某个属性的类型或者某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

jdk5.0新增

在集合中使用泛型

  1. 集合接口或集合类在jdk5.0时都修改为带泛型的结构

  2. 在实例化集合类时,可以指明具体的泛型类型

  3. 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型

    比如:add(E e) —->实例化以后:add(Integer e)

  4. 注意点:泛型的类型必须是一个类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换

  5. 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

//举例
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(123);
list.add(98);
//list.add("Joey");//此行不过编译	//因为设置了泛型,编译时就会进行类型检查

for (Integer score : list) {
    int stuScore = score;//避免了强转操作
    System.out.println(stuScore);
}
//用Iterator
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
    int stuScore = iterator.next();
    System.out.println(stuScore);
}


//以HashMap为例
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("1", 1);
map.put("2", 2);
map.put(1, 1);//编译时会报错
Set<Map.Entry<String, Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator1 = entry.iterator();
while (iterator.hasNext()) {
    Map.Entry<String, Integer> e = iterator1.next();
    String key = e.getKey();
    Integer value = e.getValue();
    System.out.println(key + "----" + value);
}

自定义泛型结构(类、结构、方法)

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>

  2. 泛型类的构造器如下:public GenericClass(){}

    而下面是错误的:public GenericClass(){}

  3. 实例化后,操作原来泛型的位置的结构必须与指定的泛型类型一致

  4. 泛型不同的引用不能相互赋值

    尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有一个ArrayLIst被加载到JVM中

  5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object.经验:泛型要使用一路都用。要不用,一路都不要用

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

  7. jdk1.7,泛型的简化操作:ArrayList<Fruit>flist=new ArrayList<>();

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

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

  10. 异常类不能是泛型类

  11. 不能使用new E[] 。但是可以:E[] elements=(E[])new Object[capacity];

    ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组

  12. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型

    • 子类不能保留父类的泛型:按需实现
      • 没有类型 擦除
      • 具体类型
    • 子类保留父类的泛型:泛型子类
      • 全部保留
      • 部分保留
  • 结论:子类必须是“富二代”,子类处理指定或保留父类的泛型,还可以增加自己的泛型
//自定义泛型类
public class Order<E> {
    String orderName;
    int orderId;

    //类的内部结构就可以使用类的泛型
    E orederE;

    public Order() {
    }

    public Order(String orderName, int orderId, E orderE) {
        this.orderName = orderName;
        this.orderId = orderId;
        this.orederE = orderE;
    }

    public E getOrderE() {
        return orederE;
    }

    public void setOrederE(E orederE) {
        this.orederE = orederE;
    }

    @Override
    public String toString() {
        return "Order{" + "orderName='" + orderName + '\'' + ", orderId=" + orderId + ", orederE=" + orederE + '}';
    }

    //用自定义的泛型类
    public static void main(String[] args) {
        /*
        如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型魏Object类型
            如果定义了类是带泛型的,建议在实例化时要知名类的泛型
        建议实例化时指明泛型类型
         */
        //此时orderE只能是String类型
        Order<String> order = new Order<String>("orderAA", 1001, "order:AA");
        //order.setOrederE(123);    //编译时报错,因为不是String
        System.out.println(order);
    }
}

泛型的理解

泛型的概念

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

泛型的引入背景

把元素的类型设计成一个参数,这个类型参数叫做泛型。

泛型在集合中的使用

集合中使用泛型之前的例子

@Test
public void test1(){
    ArrayList list=new ArrayList();
    //需求:存放学生的成绩
    list.add(78);
    list.add(76);
    list.add(89);
    list.add(88);
    //问题一:类型不安全
    //list.add("Tom");
    for(Object score:list){
        //问题二:强转时,可能出现classCastException
        int stuScore = (Integer) score;
        System.out.println(stuScore);
    }
}
{
    @Test
    public void test2() {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(78);
        list.add(87);
        list.add(99);
        list.add(65);
        //编译时,就会进行类型检查,保证数据的安全
        //list.add("Tom");

        /*
        方式一:
        for(Integer score:list){
        //避免了强转操作
        int stuScore = score;
        
        System.out.println(stuScore);
        }
        */

        //方式二
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            int stuScore = iterator.next();
            System.out.println(stuScore);
        }
    }

    //在集合中使用泛型的情况:以HashMap为例
    @Test
    public void test3() {
        //Map<String,Integer> map = new HashMap<String,Integer>();
        //jdk7新特性:类型推断
        Map<String, Integer> map = new HashMap<>();
        map.put("Tom", 87);
        map.put("Jerry", 87);
        map.put("Jack", 67);

        //map.put(123,"ABC");
        //泛型的嵌套
        Set<Map.Entry<String, Integer>> entry = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> e = iterator.next();
            String key = e.getKey();
            Integer value = e.getValue();
            System.out.println(key + "-----" + value);
        }
    }
}

自定义泛型类、泛型接口、泛型方法

public class Order<T>{
    String orderName;
    int orderId;
    //类的内部结构就可以使用类的泛型
    T orderT;
    public Order(){
        T[] arr=(T[]) new Object[10];
    }
    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 void show(){
         
    }
    public static <E> List<E> copyFromArrayToList(E[] arr){
        ArrayList<E> list = new ArrayList<>();
        for(E e:arr){
            list.add(e);
        }
        return list;
    }
}
  • 注意点

    1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>

    2. 泛型类的构造器如下:public GenericClass(){}

      而下面是错误的:public GenericClass(){}

    3. 实例化后,操作原来泛型的位置的结构必须与指定的泛型类型一致

    4. 泛型不同的引用不能相互赋值

      尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有一个ArrayLIst被加载到JVM中

    5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object.经验:泛型要使用一路都用。要不用,一路都不要用

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

    7. jdk1.7,泛型的简化操作:ArrayList<Fruit>flist=new ArrayList<>();

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

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

    10. 异常类不能是泛型类

    11. 不能使用new E[] 。但是可以:E[] elements=(E[])new Object[capacity];

      ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组

    12. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型

      • 子类不能保留父类的泛型:按需实现
        • 没有类型 擦除
        • 具体类型
      • 子类保留父类的泛型:泛型子类
        • 全部保留
        • 部分保留
    • 结论:子类必须是“富二代”,子类处理指定或保留父类的泛型,还可以增加自己的泛型

应用场景举例

  • DAO.java

    定义了操作数据库中的表的通用操作。 ORM思想(数据库中的表和Java中的类对应)

    public class DAO<T>{//表的共性操作的DAO
        //添加一条记录
        public void add(T t){
            
        }
        //删除一条记录
        public boolean remove(int index){
            return false;
        }
        //修改一条记录
        public void update(int index,T t){
            
        }
        //查询一条记录
        public T getIndex(int index){
            return null;
        }
        //查询多条记录
        public List<T> getForList(int index){
            return null;
        }
        //泛型方法
        //举例:获取表中一共有多少条记录?获取最大的员工入职时间?
        public <E> E getValue(){
            return null;
        }
    }
    
  • CustomerDAO.java

    public class CustomerDAO extends DAO<Customer>{//只能操作某一表的DAO
        
    }
    
  • StudentDAO.java

    public class StudentDAO extends DAO<Student>{
        
    }
    

泛型在继承上的体现

import org.junit.Test;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;

public class GenericTest {
    //泛型在继承方面的体现
    //虽然类A是类B的父类,但是G<A>和G<B>二者不具备子父类关系,而这时并列关系
    //补充:类A是类B的父类,A<G>是B<G>的父类
    @Test
    public void test1() {
        Object obj = null;
        String str = null;
        obj = str;

        Object[] arr1 = null;
        String[] arr2 = null;
        arr1 = arr2;
        //编译不通过
        //Date date = new Date();
        //str = date;
        List<Object> list1 = null;
        List<String> list2 = new ArrayList<>();
        //此时的list1和list2的类型不具有子父类关系
        //编译不通过
        //list1= list2;
        /*
        反证法
        假设list1 = list2;
        list1.add(123);//导致混入非String的数据。出错
        */
    }

    public void show1(List<String> list) {

    }

    public void show(List<Object> list) {

    }

    @Test
    public void test2() {
        AbstractList<String> list1 = null;
        List<String> list2 = null;
        ArrayList<String> list3 = null;
        list1 = list3;
        list2 = list3;
        List<String> list4 = new ArrayList<>();
    }
}

通配符

  1. 通配符的使用

    涉及通配符的集合的数据的写入和读取

    /*
    通配符的使用
    通配符:?
    类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?>
    */
    @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);
        print(list);
    }
    
    public void print(List<?> list) {
        Iterator<?> iterator = list.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println(obj);
        }
    }
    
  2. 有限制条件的通配符的使用

    /*
    ? extends A:
    		G<? extends A>	可以作为G<A>和G<B>的父类,其中B是A的子类
    ? super A:
    		G<? super A>	可以作为G<A>和G<B>的父类,其中B是A的父类
    */
    @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>();
        
        list1 = list3;
        list1 = list4;
        //list1 = list5;
        
        //list2 = list3;
        list2 = list4;
        list2 = list5;
        
        //读取数据
        list1 = list3;
        Person p = list1.get(0);
        //编译不通过
        //Student s =list1.get(0);
        
        list2 = list4;
        Object obj = list2.get(0);
        //编译不通过
        //Person obj = list2.get(0);
        
        //写入数据
        //编译不通过
        //list1.add(new Student());
        
        //编译通过
        list2.add(new Person());
        list2.add(new Student());
    }
    

IO流

File类

java.io.Flie类:文件和文件目录路径的抽象表示形式,与平台无关

  • File能新建、删除、重命名文件和目录,但File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流
  • 想要在Java程序中表示一个真实存在的目录或文件,那么必须有一个File对象,但是Java程序中的File对象,可能没有一个真实存在的文件或目录
  • File对象可以作为参数传递给流的构造器
  • File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成
  • 后续File类的对象常会作为参数传递到流的构造器中,指明或写入的“终点”.
构造器
File(File parent,String child)以parent为父路径
child为子路径
File(String pathname)pathname:路径
File(String parent,String chlid)根据一个父类File对象
child为子路径
File file1=new File("hello.txt");//相对于当前module路径下
File file2=new File("C:\\JavaTest\\hello");//绝对路径;“\\”转义后为“\”
//windows和DOS系统默认使用“\”分割
//UNIX和URL使用“/”分割
//由于Java提供了`public static Final String separator `.可以随便写(跨平台性)
File file02=new File("d:"+File.separator+"JavaTest"+File.separator+"info.txt");
File file002=new File("d:\\JavaTest/hi.txt");
System.out.println(file1);//输出hello.txt;该文件不一定存在

File file3=new File("C:\\JavaTest","JavaSenior");//C:\\JavaTest\\JavaSenior文件目录

File file4=new File(file3,"hi.txt");//在C:\\JavaTest\\JavaSenior\\hi.txt文件

路径

相对路径:相对于某个路径下,指明的路径

绝对路径:包含盘符在内的文件或文件目录的路径

常用方法

  • 获取

    • public String getAbsolutePath():获取绝对路径

    • public String getPath():获取路径

    • public String getName():获取名称

    • public string getParent():获取上层文件目录路径。若无,返回null

    • public long length():获取文件长度(即:字节数)。不能获取目录的长度。

    • public long lastModified():获取最后一次的修改时间,时间戳;毫秒值

      System.out.println(new Date(file1.lastModified()));//可以把修改时间的时间戳转为易读时间格式

    • 以下方法适用于文件目录

    • public String[] list():获取指定目录下的所有文件或文件目录的名称数组

    • public File[] listFiles():获取指定目录下的所有文件或文件目录的File数组

  • 重命名

    • public boolean renameTo(File dest):把文件重命名为指定的文件路径
  • 判断

    • 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中删除不走回收站

      要删除一个文件目录,请注意该文件目录内不能包含文件或文件目录

方法描述
exists()文件或目录是否存在
getName()获取文件或目录名称
getPath()获取文件或目录路径
isDirectory()是否为目录
isFile()是否为文件
createNewFile()若文件不存在,则创建新文件
delete()删除文件或目录
renameTo(File)重命名文件或目录
mkdir()创建单级目录
mkdirs()创建多级目录
listFiles()列出该路径下所有文件或目录,集合类型为File
//文件的创建与删除
File file1=new File("hi.txt");
if(!file1.exists()){
    file1.createNewFile();
    System.out.println("创建成功");
}else{
    file1.delete();
    System.out.println("删除成功");
}

//文件夹的创建与删除
File file2=new File("C:\\IOTest\\io1");
if(file2.mkdir()){
    System.out.println("创建成功");
}else{
    System.out.println("创建失败");
}

例题

File file =new File("D:\\io\\io1\\hello.txt");
//创建一个与file同目录下的另外一个文件,文件名为:haha.txt
File destFile = new File(file.getParent(),"haha.txt");
if(destFile.createNewFile){
    System.out.println("创建成功");
}

//判断指定目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称
String[] list=f.list();
for(String l:list){
    if(l.endWith(".jpg")){
        System.out.println(l);
    }
}
//遍历指定目录所有文件名称,包括子文件目录中的文件。
private static void func(File file){
    File[] fs = file.listFiles();
    for(File f:fs){
        if(f.isDirectory())	//若是目录,则递归打印该目录下的文件
            func(f);
        else 	//若是文件,直接打印
            System.out.println(f.getName());
    }
}

//删除指定文件目录及其下的所有文件
@Test
public void testRecusionDelete(){
    File file = new File("/IOTest/");
    System.out.println(recusionDelete(file));
}

public static boolean recusionDelete(File file){
    if(file.isDirectory()&&file.exists()){
        File[] files = file.listFiles();
        if(files!=null){
            for(File f:files){
                recursionDelete(f);
            }
        }
    }
    return file.delete();
}

IO流概述

I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等

Java程序中,对于数据的输入/输出操作以”流(stream)“的方式进行

java.io包下提供了各种”流“类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据

  • **输入input:**读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
  • **输出output:**将程序(内存)数据输出到磁盘、光盘等存储设备中

标准的输入过程

  1. 创建File类的对象,指明读取的数据来源。(要求此文件一定要存在)

  2. 创建相应的输入流,将File类的对象作为参数,传入流的构造器中

  3. 具体的读入过程

    创建相应的byte[] 或 char[]

  4. 关闭流资源

程序中出现的异常需要使用try-catch-finally进行处理

标准的输出过程

  1. 创建File类的对象,指明写出的数据的位置。(不要求此文件一定要存在)

  2. 创建相应的输出流,将File类的随想作为参数,传入流的构造器中

  3. 具体的写出过程

    write(char[]/byte[] buffer,0,len)

  4. 关闭流资源

程序中出现的异常需要使用try-catch-finally进行处理

分类

  • 数据单位不同

    • 字节流(8bit)byte,非文本数据(图片、视频……
    • 字符流(16bit)char,更适合处理文本文件
  • 按数据流的流向不同分为

    • 输入流
    • 输出流
  • 按流的角色的不同分为

    • 节点流,作用在文件上
    • 处理流,总用在已有的流(处理流、节点流)上
(抽象基类)字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter
  1. Java的IO流共涉及40多个类,实际上非常规则,都是从如上4个抽象基类派生的
  2. 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

流的体系结构

抽象基类节点流(或文件流)缓冲流(处理流的一种)
InputStreamFiileInputStreamBufferedInputStream
OutputstreamFileOutputStramBufferedOutputStream
ReaderFileReaderBufferedReader
writerFileWrterBufferedWriter

在这里插入图片描述

节点流(或文件流)

FileInputStream

FileOutputStream

FileReader

FileWriter

抽象基类节点流(或文件流)缓冲流(处理流的一种)
InputStreamFileInputStream(read(byte[] buffer))BufferedInputStream(read(byte[] buffer))
OutputStreamFileOutputStream(write(byte[] buffer,0,len))BufferedOutputStream(write(byte[] buffer,0,len)/flush())
ReaderFileReader(read(char[] cbuf))BufferedReader(read(char[] cbuf)/readline())
WriterFileWriter(writer(char[] cbuf,0,len))BufferedWriter(write(char[] cbuf,0,len)/flush())

FileReader的使用

  1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
  2. 异常的处理:为例保证流资源一定可以执行关闭操作。需要使用try-catch-finally进行处理
  3. 读入的文件一定要存在,否则就会报FileNotFoundException
构造器描述
FileReader(String fileName)创建文件字符输入流,并指定文件路径
FileReader(File file)创建文件字符输入流,并指定文件对象
FileReader(String fileName,Charset charset)创建文件字符输入流,并指定文件路径和字符集
FileReader(File file,Charset charset)创建文件字符输入流,并指定文件对象和字符集
/*
将day09下的hello.txt文件内容读入程序中,并输出到控制台
*/
@Test
public void testFileReader() throws IOException{
    try {
        //1. 实例化File类的对象,指明要操作的文件
        File file = new File("hello.txt");//相对于当前Module
        //2. 提供具体的流
        FileReader fr = new FileReader(file);
        //3. 数据的读入
        int data = fr.read();
        //read();返回读入的一个字符。如果达到文件末尾,返回-1
        while (data != -1) {
            System.out.print((char) data);
            data = fr.read();
        }
        //简化版
        //int data;
        //while((data = fr.read) != -1){
        //	System.out.print((char)data);
        //}
    } catch (IOException e) {
        System.err.println(e.getMessage());
    } finally {
        //4. 流的关闭操作
        try {
            if (fr != null)
                fr.close();//流必须手动回收
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//对read()操作升级:使用read的重载方法
@Test
public void testFileReader1() {
    try {
        //1. File类的实例化
        File file = new File("hello.txt");
        //2. FileReader流的实例化
        FileReader fr = new FileReader(file);
        //3. 读入的操作
        //read(char[] cbuff):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
        char[] cbuf = new char[5];
        int len;
        while ((len = fr.read(cbuf)) != -1) {
            /*for (int i = 0; i < len; i++) {
                System.out.print(cbuf[i]);
            }*/
            //简化
            System.out.print(new String(cbuf,0,len));//cbuf数组中角标从0开始到len个字符
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4. 资源的关闭
        if (fr != null) {
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileWriter的使用

从内存中写出数据到硬盘的文件里

  1. 输出操作,对应的File可以不存在。并不会报异常

    File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件

    File对应的硬盘中的文件如果存在

    ​ 如果流使用的构造其实:FileWriter(file,false)/FileWriter(file):对原有文件的覆盖

    ​ 如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容

构造器描述
FileWriter(String filePath,boolean append)创建文件字符输出流,并指定文件路径
FileWriter(File file,boolean append)创建文件字符输出流,并指定文件对象
FileWriter(String fileName,Charset charset,boolean append)创建文件字符输出流,并指定文件路径和字符集
FileWriter(File file,Charset charset,boolean append)创建文件字符输出流,并指定文件对象和字符集
@Test
public void testFileWriter(){
    FileWriter fw = null;
    try{
        //提供File类的对象,指明写出到的文件
        File file = new File("hello.txt");
        //提供FileWriter的对象,用于数据的写出
        FileWriter fw = new FileWriter(file);
        //写出的操作
        fw.write("I have a dream!\n");
        fw.write("you need to have a dream!");
    }catch(IOException e{
        e.printStackTrace();
    }finally{
    	//流资源的关闭
        if(fw != null){
   	 		try{
                lose();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

文本的复制

@Test
public void testFileReaderFileWriter(){
    FileReader fr = null;
    FileWriter fw = null;
    try{
        //1. 创建File类的对象,指明读入和写出的文件
        File srcFile = new File("hello.txt");
        File destFile = new File("hello02.txt");
        
        //不能使用字符流来处理图片等字节数据
        //File srcFile = new File("爱情与友情.jpg);
        //File destFile = new File("爱情与友情1.jpg");
        
        //2. 创建输入流和输出流的对象
        fr = new FileReader(srcFile);
        fw = new FileWriter(destFile);
        
        //3. 数据的写入和写出操作
        char[] cbuf = new char[5];
        int len;//记录每次读入到cbuf数组中的字符的个数
        while((len = fr.read(cbuf)) != -1){
            //每次写出len个字符
            fw.write(cbuf,0,len);
        }
    }catch (IOEception e){
        e.printStackTrace();
    }finally{
        //4. 关闭资源
        try{
            if(fw != null){
                fw.close();
            }
        }catch(IOException e){
            e.printStackTrace();
        }
        try{
            if(fr != null){
                fr.close();
            }
        }catch(IOException e){
            e.printStackTrace();
        }
        /*
        另一种方式
        try{
        	if(fw != null){
        	fw.close();
        	}
        }catch(IOException e){
        	e.printStackTrace();
        }finally{
        	try{
        		if(fr != null){
        		fr.close();
        		}
        	}catch(IOException e){
        		e.printStackTrace();
        	}
        }
        */
    }
}

FileInputStream/FileOutStream的使用

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

FileInputStream

  • FileInputStream(String path) 文件地址
  • FileInputStream(File file) 文件对象
try{
    FileInputStream fis = new FileInputStream("/IOTest/test01/hello.java");
    int b;
    /*
    从输入流中读取下一个字节的数据
    值字节以0到255范围内的int形式返回
    如果以达到流末尾而没有可用字节,则返回值-1
    此方法会阻塞,知道输入数据可用、检测到流结束或抛出异常为止

    @return		数据的下一个字节,如果达到流的末尾,则为-1
    @exception	IOException
    public abstract int read() thorows IOException;
    */
    while((b = fis.read())!=-1){
        System.out.print((char)fis);
    }
}catch(FileNotFoundException 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 Sting(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();
            }
        }
    }
}

FileOutputStream

  • FileOutputStream(String path) 创建文件输出字节流,并指定文件路径,默认覆盖文件(创建流便清空文件),慎用
  • FileOutputStream(String path,boolean append) 创建文件输出字节流,并指定文件路径,是否追加文件,默认覆盖文件(false,创建流便清空文件)
  • FileOutPutStream(File file) 创建文件输出字节流,并指定文件对象,默认覆盖文件(创建流便清空文件),慎用
  • FileOutputStream(File file,boolean append) 创建文件输出字节流,并指定文件对象,是否追加文件,默认覆盖文件(false,创建流便清空文件)
FileOutputStream fos = null;
try{
    File file = new File("/IOTest/test01/a.txt");
    if(!file.exsits()){
        /*
        此处不判断文件是否存在的原因是:
        在使用写入文件时,若文件不存在,会自动创建文件
        */
        if(!file.getParentFile().mkdir()){
            System.out.println("目录创建失败");
        }
    }
    fos = new FileOutPutStream(file);
    String str = "Hello,World";//需要写入的字符串
    byte[] bytes = str.getBytes();//转为字节数组
    fos.write(bytes);
}catch(IOException e){
    e.printStackTrace();
}finally{
    if(fos != null){
        try{
            fos.cloase();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}
/*
图片的复制
被复制的文件存在为前提
*/

@Test
public void testFileInputOutputStream(){
    FileInputStream fis = null;
    FileOutputStream for = null;
    try{
        //造文件
        File srcFile = new File("/IOTest/test01/a.jpg");
        File destFile = new File("/IOTest/test01/a2.jpg");
        //造流
        fis = new FileInputStream(srcFile);
        fos = new FileOutputStream(destFile);

        //复制的过程
        byte[] buffer = new byte[1024];//缓冲区,字节数组
        int len;
        while ((len = fis.read(buffer)) !=-1){
            fos.write(buffr,0,len);
        }
    }catch(IOException e){
        e.printStackTrace();
    }finally{
        //关闭流
        if(fos != null){
            try{
                fos.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        if(fis != null){
            try{
                fis.close();
            }catch(IOEception e){
                e.printStackTrace();
            }
        }
    }
}

相对路径在IDEA和Eclipse中使用的区别?

  • IDEA

    如果开发使用JUnit中的单元测试,相对路径即为当前Module下。

    如果使用main()测试,相对路径即为当前的Project下

  • Eclipse

    不管使用单元测试方法还是使用main()测试,相对路径都是当前的Project下

缓冲流的使用

缓冲流涉及到的类

BufferedInputStream

BufferedOutputStream

BufferedReader

BufferedWriter

作用

提高流的读取、写入的速度

(内部提供了一个缓冲区,缺省使用8192个字节(8kb)的缓冲区

public class BufferedInputStream extends FilterInputStream{
    private static int DEFAULT_BUFFER_SIZE = 8192;
}

缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:

  • BufferedInputStream 和BufferedOutputStream
  • BufferedReader 和 BufferedWriter

典型代码

使用BufferedInputStream和BufferedOutputStream:处理非文本文件

@Test
public void BufferedStreamTest() throws FileNotFoundException{
    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;
    try{
        //1. 造文件
        File srcFile = new File("爱情与友情.jpg");
        File desFile = new File("爱情与友情3.jpg");
        //2. 造流
        //2.1 造节点流
        FileInputStream fis = new FileInputStream((srcFile));
        FileOutputStream fos = new FileOutputStream(destFile);
        //2.2 制造缓冲流
        bis = new BuffredInputStream(fis);
        bos = new BufferedOutputStream(fos);
        //3.复制的细节:读取、写入
        byte[] buffer = new byte[10];
        int len;
        while((len = bis.readd(buffer)) != -1){
            bos.write(buffer,0,len);
            //bos.flush();//刷新缓冲区
        }
    }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();
            }
        }
        //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略
        //fos.close();
        //fis.close();
    }
}


使用BufferedReader和BufferedWriter

@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")));
        //使用String
        String data;
        while((data = br.readLine()) != null){
            bw.write(data);//data中不包含换行符
            bw.newLine();//提供换行的操作
        }
    }
}

转换流的使用

转换流涉及到的类

InputStreamReader:将一个字节的输入流转换为字符的输入流

解码:字节、字节数组 —>字符数组、字符串

OutputStreamWriter:将一个字符的输出流转换为字节的输出流

编码:字符数组、字符串 —>字节、字节数组

说明:编码决定了解码的方式

作用

提供字节流与字符流之间的转换

图示

在这里插入图片描述

典型实现

//读
@Test
public void test1() throws IOException{
    FileInputStream fis = new FileInputStream("dbcp.txt");
    FileInputStream fis = new FileInputStream("dbcp.txt");//使用系统默认的字符集
    //参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用系统默认的字符集
    
    char[] cbuf = new char[20];
    int len;
    while((len = isr.read(cbuf)) !=-1){
        String str = new String(cbuf,0,len);
        System.out.print(str);
    }
    isr.close();
}
/*
此时处理异常的话,仍然应该使用try-catch-finally
综合使用InputStreamReader和OutputStreamWriter
*/
@Test
public void test2() throws Exception{
    //1. 造文件、造流
    File file1 = new File("dbcp.txt");
    File file2 = new File("dbcp_gbk.txt");
    
    FileInputStream fis = new FileInputStream(file1);
    FileOutputStream fos = new FileOutputStream(file2);
    
    InputStreamReader isr = new InputStreamReader(fis,"utf-8");
    OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
    
    //读写过程
    char[] cbuf = new char[20];
    int len;
    while((len = isr.read(cbuf)) != -1){
        osw.write(cbuf,o,len);
    }
    
    //关闭资源
    isr.close();
    osw.close();
}

说明

文件编码的方式:(比如:GBK),决定了解析时使用的字符集(也只能是GBK)

常见编码表

计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识计算机只能识别二进制数据,早期由来是电信号.为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表
这就是编码表这就是编码表

  • ASCII:美国标准信息交换码
    • 用一个字节的7位可以表示
  • ISO8859-1:拉丁码表。欧洲码表
    • 用一个字节的8位表示
  • GB2312:中国的中文编码表。最多两个字节编码所有字符
  • GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
  • Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都有两个字节来表示
  • UTF-8:变长的编码方式,可用1-4个字节来表示一个字符

对后面学习的启示

客户端/浏览器端 — 后台(java,GO,Python,Node.js,php) — 数据库

要求前前后后使用的字符集都要统一:UTF-8

其它的流的使用

标准的输入输出流:

//System.in:	标准的输入流,默认从键盘输入
//System.out:	标准的输出流,默认从控制台输出

//修改默认的输入和输出行为
//System类的setIn(InputStream is)/ setOut(printStream ps)
//方式重新指定输入和输出的流
/*
 从键盘输入字符串要求读取到整行字符串转成大写输出。然后继续进行输入操作,直至输入"e"或者“exit”时,退出程序。
 方法一:使用Scanner实现,调用next()返回一个字符串
 方法二:使用System.in实现。System.int --> 转换流 ---> BufferedReader的readLine()
 */
public static void main(String[] args) {
    try {
        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(isr);

        while (true) {
            System.out.println("请输入字符串");
            String data = br.readLine();
            if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
                System.out.println("程序结束");
                break;
            }
            String upperCase = data.toUpperCase();
            System.out.println(upperCase);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class MyInput {
    //Read a string from the keyboard
    public static String readString() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        //Declare and initialize the string
        String string = "";

        //Get the string from the keyboard
        try {
            string = br.readLine();
        } catch (IOException e) {
            System.out.println(e);
        }

        //Return the string obatained from the keyboard
        return string;
    }

    //Read an int value from the keyboard
    public static int readInt() {
        return Integer.parseInt(readString());
    }

    //Read a double value from the keyboard
    public static double readDouble() {
        return Double.parseDouble(readString());
    }

    //Read a byte value from the keyboard
    public static double readByte() {
        return Byte.parseByte(readString());
    }

    //Read a short value from the keyboard
    public static double readShort() {
        return Short.parseShort(readString());
    }

    //Read a long value from the keyboard
    public static double readLong() {
        return Long.parseLong(readString());
    }

    //Read a float value from the keyboard
    public static double readFloat() {
        return Float.parseFloat(readString());
    }
}

打印流

实现将基本数据类型的数据格式转化为字符串输出

打印流:PrintStreamPrintWriter

  • 提供一系列重载的print()和println()方法,用于多种数据类型的输出
  • PrintStream和PrintWriter的输出不会抛出IOException异常
  • PrintStream和PrintWriter有自动flush功能
  • PrintStream打印的所有字符都使用平台默认字符编码转换为字节。再需要写入字符而不是写入字节的情况下,应该使用PrintWriter类
  • System.out返回的是PrintStream的实例

数据流

DataInputStream和DataOutputStream

用于读取或写出基本数据类型的变量或字符串

/*
练习:将内存中的字符串、基本数据类型的变量写出到文件中
注意:处理异常的话,仍然应该使用try-catch-finally
*/
@Test
public void test3() throws IOException{
    //1. 
    DateOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
    //2. 
    dos.writeUTF("刘建辰");
    dos.flush();//刷新操作,将内存中的数据写入文件
    dos.writeInt(23);
    dos.flush();
    dos.writeBoolean(true);
    dos.flush();
    //3. 
    dos.close();
}
/*
将文件中存储的基本数据类型变量的字符串读取到内存中,保存在变量中
注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致
*/
@Test
public void test4() throws IOException{
    //1. 
    DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
    //2.
    String name = dis.readUTF();
    int age = dis.readInt();
    boolean isMale = dis.readBollean();
    
    System.out.println("name="+name);
    System.out.println("age="+ age);
    System.out.println("isMale="+isMale);
    
    //3. 
    dis.close();
}

对象流的使用

ObjectInputStream和Object’Outstream

作用

ObjectOutputStream:内存中的对象 —> 存储中的文件、通过网络传输出去

ObjectInputStream:存储中的文件、通过网络接收过来 —> 内存中的对象

对象的序列化机制

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,或通过网络将这种二进制传输到拎一个网络节点。

//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象

序列化过程

/*
序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
使用ObjectOutputStream实现
*/
@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,1001,new Account(5000)));
        oos.flush();
    }catch(IOException e){
        e.printStackTrace();
    }finally{
        if(oos != null){
            //3.
            try{
                oos.close();
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

反序列化

/*
反序列化:将磁盘文件中的对象还原为内存中的java对象
使用ObjectInputStream来实现
*/
@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 需要满足如下的要求,方可序列化
1. 需要实现Serializable
2. 当前类提供六一个全局变量:serialVersionUID
3. 处理当前Person类需要实现Serializable接口外,还必须保证其内部所有属性
也必须是可序列化的。(默认情况下,基本数据类型可序列化)
补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
*/

RandomAccesFile的使用

Path、Paths、Files的使用

装饰设计模式

  • 装饰模式是动态的给一个对象添加一些额外的功能,就增加功能来说,装饰模式比生成子类更为灵活
  • 装饰模式是在不必改变原类文件和使用继承的情况下,动态的拓展一个对象的功能。提供比继承跟多的灵活性
  • 装饰模式是创建一个包装对象,也就是使用装饰来包裹真实的对象

实现方式

  1. 装饰对象和真实对象有相同的接口/抽象类。这样客户端对象就能以和真实对象相同的方式和装饰对象交互
  2. 装饰对象包含一个真实的引用(refernce)
  3. 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象
  4. 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以子啊外部增加附加功能。在面向对象的设计中,通常是通过继承来是西安对给定类的功能拓展

网络编程

暂未学习

Java反射机制

概述

  • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

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

    正常方式:
    引入需要的包类名称
    通过new实例化
    取得实例化对象
    反射方式:
    实例化对象
    getClass方法
    得到完整的包类名称
  • Java反射机制提供的功能

    • 在运行时判断任意一个对象所属的类
    • 在运行时构造人一个类的对象
    • 在运行时判断任意一个类所具有的成员变量和方法
    • 在运行时获取泛型信息
    • 在运行时调用任意一个对象的成员变量和方法
    • 在运行时处理注解
    • 生成动态代理
  • 主要api
    • java.lang.Class:代表一个类
    • java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructo:代表类的构造器

package com.JavaTest.Reflection;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionTest {
    //反射之前
    @Test
    public void test1() {
        //1. 创建Person类的对象
        Person p1 = new Person("Joey", 22);
        //2. 通过对象调用属性和方法
        p1.setAge(10);
        System.out.println(p1.toString());
        p1.show();

        //在Person类外部,不可以通过Person类的对象调用其内部私有结构
        //比如:name、showNation()以及私有的构造器
    }

    //有了反射之后,对于Person的操作
    @Test
    public void test2() throws Exception {
        Class clazz = Person.class;
        //1. 通过反射,创建Person类的对象
        Constructor cons=clazz.getConstructor(String.class,int.class);
        Object obj =cons.newInstance("Joey",22);
        Person p=(Person) obj;
        System.out.println(obj.toString());
        //2. 通过反射,调用对象指定的属性、方法
        //属性
        Field age=clazz.getDeclaredField("age");
        age.set(p,10);
        System.out.println(p.toString());

        //调用方法
        Method show=clazz.getDeclaredMethod("show");
        show.invoke(p);

        //通过反射,可以调用Person类的私有结构。比如:私有的构造器、方法、属性
        Constructor cons1=clazz.getDeclaredConstructor(String.class);
        cons1.setAccessible(true);
        Person p1=(Person) cons1.newInstance("Jerry");
        System.out.println(p1);
        //调用私有的属性和方法
        Field name=clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1,"Tom");
        System.out.println(p1);

        Method showNation=clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        String nation = (String) showNation.invoke(p1,"中国");//相当于String nation = p1.showNation("中国")
        System.out.println(nation);
    }
}

看待反射与封装

开发中建议直接用new的方式

  • 什么时候使用反射的方式
    • 反射的特征:动态性
  • 如何看待两个技术
    • 不矛盾
    • 封装仅作提示作用

Class类

java.lang.Class类的理解

  1. 类的加载过程:

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

    加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。

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

    //获取Class的实例的方式
    //前三种需要掌握
    pulic void test{
        //方式一:调用运行时类的属性:.class
        Class<Person> clazz1 = Person.class;//加上泛型后,避免后边操作的强转
        System.out.println(clazz1);//输出"Class com.xxxx.Person"
        
        //方式二:通过运行时类的对象,调用getClass
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);
        
        //方式三:调用Class的静态方法:forName(String classPath)
        //最常用
        try{
            Class clazz3 = Class.forName("com.xxxx.Person");
            clazz3=("java.lang.String");//还可以更改实例
        	System.out.println(clazz3);
        }catch(classNotFoundException e){
            System.out.pritnln(e.getMessge)
        }
        
        
        System.out.println(clazz1 == clazz2);//true 
        System.out.pritnln(clazz2 == clazz3);//true
        
        
        //方式四:使用类的加载器:ClassLoader
        //了解
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.xxx.Person");
        System.out.println(clazz4 == clazz3);
    }
    
  3. 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

哪些类型可以有Class对象?

  1. class:

    外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

  2. interface:接口

  3. []:数组

  4. enum:枚举

  5. annotation:注解@interface

  6. primitive type:基本数据类型

  7. void

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[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
//只要数组的元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);//true

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。

  • 类的加载(Load)

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

    将class文件字节码内容加载到内存中,并将这些静态数据转换为方法区的运行时数据,然后生成一个代表这个类的java.lang.Class对象,作为方法区中数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。

  • 类的链接(Link)

    将类的二进制数据合并到JRE中

    将Java类的二进制代码合并到JVM的运行状态之中的过程。

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

    JVM负责对类进行初始化

    • 执行类构造器<clinit>()方法的过程,类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
    • 当初始化一个类的时候,如果发现其父亲还没有进行初始化,则需要先触发其父类的初始化
    • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确枷锁和同步
public class ClassLoadingTest{
    public static void main(String[] args){
        System.out.println(A.m);//100
    }
}
/*
m赋值顺序
m:类的加载
m=0:类的链接
m=300
m=100;类的初始化
要是代码块在static int m=100;下方,则m=300;
*/
class A{
    static{
        m = 300;
    }
    static int m = 100;
}
/*
第二步:链接结束后m=0
第三步:初始化后,m的值由<clinit>()方法执行决定
		这个A的类构造器<clinit>()方法由类变量的复制和静态代码块中的语句按照顺序合并产生,类似于
		<clinit>(){
			m = 300;
			m = 100;
		}
*/

类的加载器

在这里插入图片描述

类的加载器作用

  • 作用:将class文件字节码内容加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后在堆中生成一个代表这个类的的java.lang.Class对象,作为方法区中类数据的访问接口
  • 类缓存:标准的JavaSE类加载器可以按照要求查找类,但一旦某个类被加载到类加载器中,他将维持加载(缓存)一段时间。不过Jvm垃圾回收1机制可以回收这些Class对象

ClassLoader

类加载器作用是用来把类(class)装在进内存的。JVM规范定义了如下类型的类加载器

  • 引导类加载器:用C++编写,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
  • 扩展类加载器:负责jre/lib/ext目录下的jar包或_D java.ext.dirs指定目录下的jar包装入工作库
  • 系统加载类:负责java -classpath或_D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器
Bootstap_Classloader
Extension_Classloader
System_Classloader
自定义类加载器

自下往上检查类是否以装载

自上往下尝试加载类

@Test
public void test1(){
    //对于自定义类,使用系统类加载器进行加载
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    System.out.println(classLoader);//AppClassLoader

    //调用系统类加载器的getParent():获取扩展类加载器
    ClassLoader classLoader1 = classLoader.getParent();
    System.out.println(classLoader1);//ExtClassLoader
    
    //调用系统类加载器的getParent():无法获取引导类加载器
    //引导类加载器主要负责加载java的核心类库,无法加载自定义类的
    ClassLoader classLoader2 = classLoader1.getParent();
    System.out.println(classLoader2);//null,没法获取
    
    ClassLoader classLoader3 = String.class.getClassLoader();
    System.out.println(classLoader3);//null
}

使用类的加载器加载配置文件

properties:用来读取配置文件

#jdbc.properties
#可放于module或src下
user=王五
password=123abc
@Test
public void test2() throws Exception{
    Properties pros = new Properties();
    
    /*方式一
    此时文件默认在当前的module下
    FileInputStream fis = new FileInputStream(jdbc.properties);
    pros.load(fis);
    */
    
    //方式二:使用ClassLoader
    //配置文件默认识别为:当前module的src下
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    Inputstream is = classLoader.getResourceAsStream("jdbc.properties");
    pros.load(is);
    
    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    System.out.print("user\t"+user+"\tpassword"+password);
}

创建运行时类的对象

通过反射创建对应的运行时类的对象

@Test
public void test1() throws Exception{
    Class<Person> clazz = Person.class;
    /*
    newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参构造器
    要是没有空参构造器会报异常
    要想此方法正常的创建运行时类的对象,要求:
    1.运行时类必须提供空参构造器
    2.空参的构造器的访问权限得够访问。通常设置为public
    
    在javabean中要求提供一个public的空参构造器。原因:
    1.便于通过反射,创建运行时类的对象
    2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
    */
    Object obj = clazz.newInstance();//调用构造器造对象
    System.out.println(obj);//Person{name='null',age=0}
}

动态性

运行时代码可以根据某些条件改变自身结构

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

例子

//体会反射的动态性
@org.junit.Test
    public void test() {
    int num = new Random().nextInt(3);//0,1,2
    String classPath = null;
    switch (num) {
        case 0:
            classPath = "java.util.Date";
            break;
        case 1:
            classPath = "java.lang.Object";
            break;
        case 2:
            classPath = "com.xxxx.Person";
            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();
}

获取运行时类的属性结构及其内部结构

import com.JavaTest.Person.Person;
import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/*
获取当前运行类的属性结构
 */
public class FieldTest {
    @Test
    public void test1() {
        Class clazz = Person.class;
        //获取属性结构
        //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
        Field[] fields = clazz.getFields();
        for (Field f : fields) {
            System.out.println(f);
        }

        System.out.println();

        //getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f : declaredFields) {
            System.out.println(f);
        }
    }

    //权限修饰符 数据类型    变量名
    @Test
    public void test2() {
        Class clazz = Person.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f : declaredFields) {
            //1.权限修饰符
            int modifiers = f.getModifiers();
            /*
            public  1
            private 2
            default 0
            protected   4
            static  8
            final   10
            synchronized    20
            volatie 40
            */
            System.out.print(Modifier.toString(modifiers) + "\t");

            //2.数据类型
            Class<?> type = f.getType();
            System.out.print(type.getName() + "\t");
            //3.变量名
            String fName = f.getName();
            System.out.println(fName);
        }
    }
}

测试的Person类

package com.JavaTest.Person
import java.io.Serializable;

public class Creature<T> implements Serializable {
    private char gender;//性别
    public double weight;//体重

    private void breath() {
        System.out.println("生物呼吸");
    }

    public void eat() {
        System.out.println("生物进食");
    }
}

package com.JavaTest.Person
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String value() default "hello";
}
package com.JavaTest.Person
public interface MyInterface {
    void info();
}
package com.JavaTest.Person;

@MyAnnotation
public class Person extends Creature<String> implements Comparable<String>, MyInterface {
    private String name;
    int age;
    public int id;

    public Person() {

    }

    private Person(String name) {
        this.name = name;
    }

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

    @Override
    public void info() {
        System.out.println("我是一个人");
    }

    @Override
    public int compareTo(String o) {
        return 0;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }

    private String show(String nation) {
        System.out.println("My nation is " + nation);
        return nation;
    }

    public String display(String interests, int age) throws NullPointerException, ClassCastException {
        return interests + age;
    }

    private static void showDesc() {
        System.out.println("I am a lovely pereson");
    }
}

类的方法结构

package com.JavaTest.PersonTest;

import com.JavaTest.Person.Person;
import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/*
获取运行时类的方法结构
 */
public class MethodTest {
    @Test
    public void test1() {
        Class<Person> clazz = Person.class;

        //getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            System.out.println(m);
        }

        System.out.println();
        //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            System.out.println(m);
        }
    }

    /*
    @Xxxx
    权限修饰符   返回值类型   方法名(参数列表)   throws Exception{}
     */
    @Test
    public void test2() {
        Class<Person> personClass = Person.class;
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method m : declaredMethods) {
            //1.获取方法声明的注解
            Annotation[] annotations = m.getAnnotations();
            for (Annotation a : annotations) {
                System.out.println(a);
            }

            //2.权限修饰符
            System.out.print(Modifier.toString(m.getModifiers()) + "\t");

            //3.返回值类型
            System.out.print(m.getReturnType().getName() + "\t");

            //4.方法名
            System.out.print(m.getName());

            System.out.print("(");
            //5.形参列表
            Class<?>[] parameterTypes = m.getParameterTypes();//参数类型
            if (!(parameterTypes == null && parameterTypes.length == 0)) {
                for (int i = 0; i < parameterTypes.length; i++) {
                    if (i == parameterTypes.length - 1) {
                        System.out.print(parameterTypes[i].getName() + "\tages_" + i);
                    } else {
                        System.out.print(parameterTypes[i].getName() + "\tages_" + i + ",");
                    }
                }
            }
            System.out.print(")");

            //6.抛出的异常
            Class<?>[] exceptionTypes = m.getExceptionTypes();
            if (exceptionTypes.length > 0) {
                System.out.print("throws");
                for (int i = 0; i < exceptionTypes.length; i++) {
                    if (i == exceptionTypes.length - 1) {
                        System.out.print(exceptionTypes[i].getName());
                        break;
                    } else {
                        System.out.print(exceptionTypes[i].getName() + ",");
                    }
                }
            }
            System.out.println();
        }
    }
}

获取运行时类其他结构

package com.JavaTest.PersonTest;

import com.JavaTest.Person.Person;
import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class OthoserTest {

    /*
    获取运行时类的构造器
     */
    @Test
    public void test1() {
        Class<Person> personClass = Person.class;
        //getConstructors():获取当前运行时类中声明为public的构造器
        Constructor<?>[] constructors = personClass.getConstructors();
        for (Constructor c : constructors) {
            System.out.println(c);
        }
        System.out.println();
        //getDeclaredConstructors():获取当前运行时类中声明的所有构造器
        Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
        for (Constructor c : declaredConstructors) {
            System.out.println(c);
        }
    }

    /*
    获取运行时类的带泛型的父类
     */
    @Test
    public void test2() {
        Class<Person> personClass = Person.class;

        //获取运行时类的父类
        Class<? super Person> superclass = personClass.getSuperclass();
        System.out.println(superclass);

        //获取运行时类的带泛型的父类
        Type genericSuperclass = personClass.getGenericSuperclass();
        System.out.println(genericSuperclass);

        //获取运行时类的带泛型的父类的泛型
        Type genericSuperclass1 = personClass.getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass1;
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//获取泛型类型
        System.out.println(actualTypeArguments[0].getTypeName());
    }

    /*
    获取运行时类实现的接口
     */
    @Test
    public void test3() {
        Class<Person> personClass = Person.class;

        Class<?>[] interfaces = personClass.getInterfaces();
        for (Class c : interfaces) {
            System.out.println(c);
        }

        System.out.println();
        //获取运行时类的父类实现的接口
        Class<?>[] interfaces1 = personClass.getSuperclass().getInterfaces();
        for (Class c : interfaces1) {
            System.out.println(c);
        }

    }

    /*
    获取运行时类所在的包
     */
    @Test
    public void test6() {
        Class<Person> personClass = Person.class;

        Package aPackage = personClass.getPackage();
        System.out.println(aPackage);
    }

    /*
    获取运行时类声明的注解
     */
    @Test
    public void test7() {
        Class<Person> personClass = Person.class;

        Annotation[] annotations = personClass.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

调用运行时类的指定结构

属性

package com.JavaTest.PersonTest;

import com.JavaTest.Person.Person;
import org.junit.Test;

import java.lang.reflect.Field;

/*
调用运行时类中指定的结构:属性、方法、构造器
 */
public class ReflectionTest {

    /*
    效果不理想
     */
    @Test
    public void testField() throws Exception {
        Class<Person> personClass = Person.class;

        //创建运行时类的对象
        //调用无参构造器
        Person person = personClass.newInstance();

        //获取指定的属性
        //要求运行时类中属性声明为public
        //通常不参用此方法
        Field id = personClass.getField("id");
        //设置当前属性值
        //set():参数1:指明哪个对象的属性   参数2:将此属性值设置为多少
        id.set(person, 1001);//person对象的id属性设置为1001

        //获取当前对象的属性值
        //get():参数1:指明哪个对象的属性
        int pId = (int) id.get(person);
        System.out.println(pId);
    }

    /*
    如何操作运行时类中的指定的属性
    要求掌握
     */
    @Test
    public void testField1() throws InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class<Person> personClass = Person.class;

        //创建运行时类的对象
        Person person = personClass.newInstance();

        //getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
        Field name = personClass.getDeclaredField("name");

        //setAccessible(true):保证当前属性是可访问的
        name.setAccessible(true);

        //获取、设置指定对象的此属性值
        name.set(person, "Joey");

        System.out.println(name.get(person));
    }
}

方法

package com.JavaTest.PersonTest;

import com.JavaTest.Person.Person;
import org.junit.Test;

import java.lang.reflect.Method;

/*
调用运行时类中指定的结构:属性、方法、构造器
 */
public class ReflectionTest {
    /*
    非静态
     */
    @Test
    public void tset1() throws Exception {
        Class<Person> personClass = Person.class;
        //创建运行时类的对象
        Person person = personClass.newInstance();

        /*获取指定的某个方法
        getDeclaredMethod():参数1:指明获取的方法名称   参数2:指明获取的方法的形参列表
         */
        Method show = personClass.getDeclaredMethod("show", String.class);

        //保证当前方法是可访问的
        show.setAccessible(true);
        /*
        invoke():参数1:方法的调用者     参数2:给方法形参赋值的实参
                    返回值即为对应类中调用的方法的返回值
         */
        Object returnValue = show.invoke(person, "CHINA");//String nation = person.show("CHINA");
        System.out.println(returnValue);
    }

    /*
    静态
     */
    @Test
    public void test2() throws Exception {
        Class<Person> personClass = Person.class;
        Person person = personClass.newInstance();
        Method showDesc = personClass.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
        //如果调用的运行时类的方法没有返回值,则此invoke()返回null
        Object returnValue = showDesc.invoke(Person.class);
        System.out.println(returnValue);//因为返回值为void,所以returnValue为null
    }
}

构造器

package com.JavaTest.PersonTest;

import com.JavaTest.Person.Person;
import org.junit.Test;

import java.lang.reflect.Constructor;

/*
调用运行时类中指定的结构:属性、方法、构造器
 */
public class ReflectionTest {
    @Test
    public void testConstructor() throws Exception {
        Class<Person> personClass = Person.class;
        //获取指定的构造器
        //getDeclaredConstructor():参数:指明构造器的参数列表
        Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class);
        //保证此构造器是可访问的
        declaredConstructor.setAccessible(true);
        //调用此构造器创建运行时类的对象
        Person joey = declaredConstructor.newInstance("Joey");
        System.out.println(joey);
    }
}

总结

  1. 获取Class实例的三种方式

    1. Class clazz = String.class;

      不常用

      没能体现动态性

    2. Class clazz = Person.getClass();

    3. Class clazz =Class.forNmae(String classPath);

      体现反射的动态性

  2. Class类的理解

    1. Class实例对应着加载到内存中的一个运行时类
  3. 创建Class对应运行时类的对象的通用方法,代码实现。以及这样操作,需要对应的运行时类构造器方面满足的要求

    /*
    必须要有空参的构造器
    权限修饰符的权限要够。通常设置为public
    */
    Object obj = clazz.newInstance();//创建了对应的运行时类的对象
    
  4. 调用如下方法

    package com.Test;
    Class User{
        public void show(){
            System.out.println("I am a Chinese")
        }
    }
    
    Class<user> clazz = Class.forName("com.Test");
    User user = user.newInstance();
    Method show = clazz.getDeclaredMethod("show");
    show.setAccessiable(true);
    show.invoke(user);
    
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值