Java基础

泛型

PECS 原则

何时使用extends,何时使用super?为了便于记忆,我们可以用PECS原则:Producer Extends Consumer Super。

即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。

Collectionscopy()方法为例

public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++) {
            T t = src.get(i); // src是producer
            dest.add(t); // dest是consumer
        }
    }
}

需要返回Tsrc是生产者,因此声明为List<? extends T>,需要写入Tdest是消费者,因此声明为List<? super T>

异常处理

异常的体系结构

在这里插入图片描述

Throwable是异常体系的根,Error和Exception继承自它

Error表示严重的错误,是由JVM产生和抛出的,无需我们处理。

Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。

java规定:

  1. 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception
  2. 不需要捕获的异常,包括Error及其子类,RuntimeException及其子类。

反射

Java的反射是指程序在运行期可以拿到一个对象的所有信息。

Class类

除了 int 等基本类型外,Java的其他类型全部都是class(包括interface)。

class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来

获取一个classClass实例的三种方法:

  1. Class.forName(“全类名”);
  2. Hero.class;
  3. new Hero().getClass();

JVM为每个加载的classinterface创建了对应的Class实例来保存classinterface的所有信息;

获取一个class对应的Class实例后,就可以获取该class的所有信息;

访问字段

Java的反射API提供的Field类封装了字段的所有信息,但是通过反射读写字段是一种非常规方法,它会破坏对象的封装。

通过Class实例的方法可以获取Field实例

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
public class Test {
    public static void main(String[] args) throws Exception {
        Class<Student> studentClass = Student.class;
        // 获取public字段"score":
        System.out.println(studentClass.getField("score"));
        // 获取继承的public字段"name":
        System.out.println(studentClass.getField("name"));
        // 获取private字段"grade":
        System.out.println(studentClass.getDeclaredField("grade"));
    }
}

class Student extends Person {
    public int score;
    private int grade;
}

class Person {
    public String name;
}

通过上述代码运行结果如下所示

public int cn.akangaroo.reflection.Student.score
public java.lang.String cn.akangaroo.reflection.Person.name
private int cn.akangaroo.reflection.Student.grade

一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,例如,"name"
  • getType():返回字段类型,也是一个Class实例,例如,String.class
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

String类的value字段为例,它的定义是:

public final class String {
    private final byte[] value;
}

我们用反射获取该字段的信息,代码如下:

Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false

通过Field实例可以读取设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。

public class Test {
    public static void main(String[] args) throws Exception {
        // 获取Class实例
        Class<Person> personClass = Person.class;
        // 创建一个新的person对象
        Person p = new Person();
        // 获取public String name 字段
        Field name = personClass.getField("name");
        name.set(p, "hehe");
        System.out.println(p.name);
        
        // 获取private int age 字段
        Field age = personClass.getDeclaredField("age");
        // 因为是private的,所以需要设置setAccessible(true);
        age.setAccessible(true);
        age.set(p, 12);
        // 因为是private的,用反射获取对象的值。
        System.out.println(age.get(p));
    }
}

class Person {
    public String name;
    private int age;
}

调用方法

Java的反射API提供的Method对象封装了方法的所有信息:

通过Class实例的方法可以获取Method实例:

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有publicMethod(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
public class Test {
    public static void main(String[] args) throws Exception {
        Class<Student> studentClass = Student.class;
        // 获取public方法getScore,参数为String:
        System.out.println(studentClass.getMethod("getScore", String.class));
        // 获取继承的public方法getName,无参数:
        System.out.println(studentClass.getMethod("getName"));
        // 获取private方法getGrade,参数为int:
        System.out.println(studentClass.getDeclaredMethod("getGrade", int.class));
    }
}

class Student extends Person {
    public int getScore(String type) {
        return 99;
    }
    private int getGrade(int year) {
        return 1;
    }
}

class Person {
    public String getName() {
        return "Person";
    }
}

运行结果:

public int cn.akangaroo.reflection.Student.getScore(java.lang.String)
public java.lang.String cn.akangaroo.reflection.Person.getName()
private int cn.akangaroo.reflection.Student.getGrade(int)

通过Method实例可以获取方法信息:

  • getName():返回方法名称,例如:"getScore"
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters)

通过设置setAccessible(true)来访问非public方法;

public class Test {
    public static void main(String[] args) throws Exception {
        Class<Person> personClass = Person.class;
        Person person = new Person();
        // 调用普通方法
        Method normalMethod = personClass.getMethod("setName", String.class);
        String name = (String) normalMethod.invoke(person, "张三");
        System.out.println(name);
        // 调用静态方法
        Method staticMethod = personClass.getMethod("getNum");
        Integer num = (Integer) staticMethod.invoke(null);
        System.out.println(num);
        // 调用非public方法
        Method privateMethod = personClass.getDeclaredMethod("getName");
        privateMethod.setAccessible(true);
        String name1 = (String) privateMethod.invoke(person);
        System.out.println(name1);

    }
}

class Person {
    public String name;

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

    public static Integer getNum() {
        return 1024;
    }

    private String getName() {
        return this.name;
    }
}

通过反射调用方法时,仍然遵循多态原则。

public class Test {
    public static void main(String[] args) throws Exception {
        Class<Person> personClass = Person.class;
        Method m = personClass.getMethod("setAge", Integer.class);
        Integer age = (Integer) m.invoke(new Student(), 20);
        System.out.println(age);
    }
}

class Student extends Person {
    public Integer age;

    public Integer setAge(Integer age) {
        this.age = age + 1;
        return this.age;
    }

}

class Person {
    public Integer age;

    public Integer setAge(Integer age) {
        this.age = age;
        return this.age;
    }

}

调用构造方法

一般是使用new来创建一个对象;

如果使用反射来创建对象,可以调用Class提供的newInstance()方法,但是它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

Person person = Person.class.newInstance();

为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以通过Constructor实例创建一个实例对象:newInstance(Object... parameters); 通过设置setAccessible(true)来访问非public构造方法。

通过Class实例的方法可以获取Constructor实例:

  • getConstructor(Class...):获取某个publicConstructor
  • getDeclaredConstructor(Class...):获取某个Constructor
  • getConstructors():获取所有publicConstructor
  • getDeclaredConstructors():获取所有Constructor
public class Test {
    public static void main(String[] args) throws Exception {
        Constructor<Person> constructor = Person.class.getConstructor(Integer.class);
        Person person = constructor.newInstance(17);
        System.out.println(person.name + "~~" + person.age);

        Constructor<Person> constructor1 = Person.class.getConstructor(Integer.class, String.class);
        Person person1 = constructor1.newInstance(18, "张三");
        System.out.println(person1.name + "~~" + person1.age);
    }
}

class Person {
    public Integer age;
    public String name;

    public Person(Integer age) {
        this.age = age;
    }

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

多线程基础

创建线程

Java用Thread对象表示一个线程,通过调用start()启动一个新线程;

  1. 继承Thread类

    public class Test {
        public static void main(String[] args) {
            Thread thread = new MyThread();
            thread.start();
        }
    }
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("启动一个线程");
        }
    }
    
  2. 实现Runnable接口

    public class Test {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
        }
    }
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("启动一个线程");
        }
    }
    

    Java8可以使用lambda表达式创建

    public class Test {
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                System.out.println("haha~~");
            }, "A");
            t1.start();
        }
    }
    
  3. 实现Callable接口,对于Callable的说明,详见JUC

    public class Test {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask futureTask = new FutureTask(new MyCallable());
            Thread t = new Thread(futureTask);
            t.start();
            System.out.println(futureTask.get());
        }
    }
    class MyCallable implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            System.out.println("启动一个线程");
            return 1024;
        }
    }
    
    运行结果:
    启动一个线程
    1024
    

对于Callable和Runnable,有三点不同之处:

  1. 有无返回值
  2. 是否可以抛出异常
  3. call()方法和run()方法

线程状态

下面是java.lang.Thread.State枚举类中定义了六种线程的状态,可以调用线程Thread中的getState()方法获取当前线程的状态

线程状态解释
NEW线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
RUNNABLE就绪状态(调用start,等待调度)+ 正在运行
BLOCKED等待监视器锁时,陷入阻塞状态
WAITING等待状态的线程正在等待另一线程执行特定的操作(如notify)
TIMED_WAITING具有指定等待时间的等待状态
TERMINATED线程完成执行,终止状态

下图源自《Java并发编程艺术》图4-1

在这里插入图片描述

常见线程方法

关键字简介
sleep当前线程暂停
join加入到当前线程中
setPriority设置线程优先级
yield临时暂停
setDaemon守护线程

sleep

Thread.sleep(1000); 表示当前线程暂停1000毫秒 ,其他线程不受影响,该语句会抛出InterruptedException中断异常,因为当前线程sleep的时候,有可能被停止,这时就会抛出 InterruptedException

public class Test {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();

    }
}
class MyRunnable implements Runnable {
    @Override
    public void run() {
        int seconds = 0;
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("已经过去了" + ++seconds + "秒");
        }
    }
}

另外还有一种暂停的方法

TimeUnit.SECONDS.sleep(4);

join

所有进程,至少会有一个线程即主线程,即main方法开始执行,就会有一个看不见的主线程存在。

join可以理解为插队,如果没有t.join();那么System.out.println("结束");这一行语句可能不会在t线程运行结束再运行。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new MyRunnable(), "A");
        t.start();
        t.join();
        System.out.println(Thread.currentThread().getName() + "运行结束");

    }
}
class MyRunnable implements Runnable {
    @Override
    public void run() {
        int seconds = 0;
        while (seconds < 3) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
        }
    }
}

运行结果:
A已经过去了1A已经过去了2A已经过去了3秒
main运行结束

setPriority

线程优先级从1-10,最小的为1,最大的为10,默认为5。可以通过getPriority();

public class Test {
    public static void main(String[] args){
        Thread t1 = new Thread(() -> {
            int seconds = 0;
            while (seconds < 5) {
                System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
            }
        }, "A");
        Thread t2 = new Thread(() -> {
            int seconds = 0;
            while (seconds < 5) {
                System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
            }
        }, "B");
        t1.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
}

yield

临时暂停

public class Test {
    public static void main(String[] args){
        Thread t1 = new Thread(() -> {
            int seconds = 0;
            while (seconds < 5) {
                System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
            }
        }, "A");
        Thread t2 = new Thread(() -> {
            int seconds = 0;
            while (seconds < 5) {
                Thread.yield();
                System.out.println(Thread.currentThread().getName() + "已经过去了" + ++seconds + "秒");
            }
        }, "B");
        t1.setPriority(Thread.NORM_PRIORITY);
        t2.setPriority(Thread.NORM_PRIORITY);
        t1.start();
        t2.start();

    }
}

setDaemon

守护线程是为其他线程服务的线程。

在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。

守护线程通常会被用来做日志,性能统计等工作。

Thread t = new MyThread();
t.setDaemon(true);
t.start();

线程同步

多个线程同时读写共享变量的时候,会出现数据不一致的问题。

如下述代码所示,两个线程同时操作一个资源时,一个自增,一个自减,循环1000次,按道理来说应该是0,实际上最后的结果不一定为0。

public class Test 
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                ++Count.num;
            }
        }, "A");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                ++Count.num;
            }
        }, "A");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("over,num = " + Count.num);
    }
}

class Count {
    public static int num = 0;
}

这是因为++Count.num操作是不具有原子性的,所谓原子性操作是指不可中断的操作,比如赋值操作,原子操作是线程安全的

int n = 1;

而++Count.num这个操作,实际上由三个原子操作组成

  1. 取num的值
  2. num + 1
  3. 把新值赋予 num

举例:

假如t1线程取num的值,还没有进行第二步操作,t2线程也取了num的值,此时线程就是不安全的。

如下图所示,两个线程一个自增一个自减,最终结果为1.

在这里插入图片描述

Java程序可以使用synchronized关键字对一个对象进行加锁。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (Count.num) {
                for (int i = 0; i < 100; i++) {
                    ++Count.num;
                }
            }

        }, "A");
        Thread t2 = new Thread(() -> {
            synchronized (Count.num) {
                for (int i = 0; i < 100; i++) {
                    --Count.num;
                }
            }
        }, "B");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("over,num = " + Count.num);
    }
}

class Count {
    public static Integer num = 0;
}

同步方法

因为synchronized锁住的是一个对象,让线程自己选择锁对象往往会使得代码逻辑混乱,也不利于封装。建议把synchronized逻辑封装起来。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Count count = new Count();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                count.add();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                count.dec();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count.get());
    }
}

class Count {
    public static Integer num = 0;

    public void add() {
        synchronized (this) {
            ++this.num;
        }
    }

    public void dec() {
        synchronized (this) {
            --this.num;
        }
    }

    public int get() {
        return this.num;
    }
} 

上述代码中,synchronized锁住的对象是this,即是当前实例,下面两种写法是等价的。

public void add() {
    synchronized(this) {
        ++this.num;
    } 
}
public synchronized void add() { 
    ++this.num;
} 

如果对一个静态方法使用synchronized修饰时,对于静态方法,是没有this实例的,因为静态方法是针对类而不是实例,所以对静态方法添加synchronized,锁住的是该类的Class实例,即下面两者是相等的。

    public synchronized static void add() {
        // ...
    }
    public static void add() {
        synchronized (Count.class) {
            // ...
        }
    }

死锁

在死锁之前,首先要知道可重入锁的概念。

Java的线程锁是可重入的锁。可重入锁意味着能被同一个线程反复获取的锁,就叫做可重入锁,如下代码所示

public class Test {
    public static void main(String[] args)  {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    System.out.println("第1次获取锁,这个锁是:" + this);
                    int index = 1;
                    while (true) {
                        synchronized (this) {
                            System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
                        }
                        if (index == 5) {
                            break;
                        }
                    }
                }
            }
        }).start();
    }
}

上述代码如果使用lambda表达式创建,会出现语法报错,因为lambda表达式中的this指向的是所在外部类,而匿名内部类中this指的是匿名内部类当前对象。

        new Thread(()->{
            synchronized (this){ // 此处报错
                // do something
            }
        }).start();

一个线程可以获取一个锁后,再继续获取另一个锁,如下代码可能发生死锁。

public class Test {
    public static void main(String[] args) {
        Zone z1 = new Zone("z1");
        Zone z2 = new Zone("z2");

        new Thread(()->{
            synchronized (z1) {
                System.out.println("占据z1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("尝试占据z2");
                synchronized (z2) {
                    System.out.println("成功占据z2");
                }
            }
        }).start();

        new Thread(()->{
            synchronized (z2) {
                System.out.println("占据z2");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("尝试占据z1");
                synchronized (z1) {
                    System.out.println("成功占据z1");
                }
            }
        }).start();
    }
}
class Zone {
    String name;

    public Zone(String name) {
        this.name = name;
    }
}

如何解决,使两个线程获取的锁的顺序一致即可。

public class Test {
    public static void main(String[] args) {
        Zone z1 = new Zone("z1");
        Zone z2 = new Zone("z2");

        new Thread(()->{
            synchronized (z1) {
                System.out.println("占据z1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("尝试占据z2");
                synchronized (z2) {
                    System.out.println("成功占据z2");
                }
            }
        }).start();

        new Thread(()->{
            synchronized (z1) {
                System.out.println("占据z1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("尝试占据z2");
                synchronized (z2) {
                    System.out.println("成功占据z2");
                }
            }
        }).start();
    }
}
class Zone {
    String name;

    public Zone(String name) {
        this.name = name;
    }
}

线程交互(wait和notify)

百度百科:生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

java中使用wait和notify来解决线程交互的问题

public class Test {
    public static void main(String[] args) {
        TaskQueue taskQueue = new TaskQueue();
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(10);
                    taskQueue.put();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者").start();
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(100);
                    taskQueue.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者").start();
    }
}

class TaskQueue {
    private LinkedList<Integer> list = new LinkedList<>();
    private static int MAX_SIZE = 10;

    public synchronized void put() throws InterruptedException {
        // 判断
        while (list.size() == MAX_SIZE) {
            System.out.println("容量已满~~");
            this.wait();
        }
        // 添加
        int item = (int) (Math.random() * 10);
        list.add(item);
        System.out.println(Thread.currentThread().getName() + "添加了" + item);
        // 通知
        this.notifyAll();
    }

    public synchronized void get() throws InterruptedException {
        // 判断
        while (list.isEmpty()) {
            this.wait();
        }
        // 消费
        Integer remove = list.remove();
        System.out.println(Thread.currentThread().getName() + "消费了" + remove);
        // 通知
        this.notifyAll();
    }
}

参考

可重入锁详解(什么是可重入)

Java教程

Lambda表达式和匿名内部类

正则表达式

final
http://www.51gjie.com/java/759.html
https://blog.youkuaiyun.com/sinat_32336967/article/details/94761771

Java中转义字符的作用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值