一些JavaSE学习过程中的思路整理(二)(主观性强,持续更新中...)

一些JavaSE学习过程中的思路整理(二)(主观性强,持续更新中...)

未经作者允许,不可转载,如有错误,欢迎指正o( ̄▽ ̄)o

将一个子类的引用对象赋值给超类的对象(多态)

  • 赋予了子类对象的超类对象只能调用超类中定义的public成员变量和方法
  • 如果子类重写了超类中的方法,就会调用子类中的方法(这里真的有点搞心态)
  • 关于多态:具体使用有两种常见方式(核心原理都是将子类的对象赋值给超类的对象引用)
    • 定义方法时形参类型为父亲,传入的类型为子类类型参数
    • 定义方法时返回类型为父亲,实际调用时返回的类型子类对象

建议自己写个小的测试demo体会一下,下面是我的测试代码

public class Test1 {
    public static void main(String[] args) {
        VIP vip = new VIP();
        SuperVIP superVIP = new SuperVIP();
        Owner owner = new Owner();
        //这里owner对象的custom成员变量所属的类是两个VIP类的超类
        owner.setCustom(superVIP);
        owner.settlement();
        //custom本质上是一个SuperVIP的对象的引用
        Custom custom = owner.getCustom();
        //这里调用的是子类重写后的方法
        custom.buyBook();
        //这个num是父类的public成员变量,但是无法获取custom的私有成员变量
        //private修饰的成员变量只能在同一个类中直接访问,通常用该类的public方法访问
        System.out.println(custom.num);
        //System.out.println(custom.getName());也无法获取子类的私有成员属性
        SuperVIP superVIP1 = (SuperVIP) custom;
        //之所以能强制类型转化是因为custom本质上就是SuperVIP对象引用
        //所以说继承是多态的前提
        System.out.println(superVIP1.getName());
    }
}

class Custom {
    private int money = 50;
    public int num = 100;
    public void buyBook() {
        System.out.println("店主买书不花钱");
    }
}

class VIP extends Custom {
    public void buyBook() {
        System.out.println("普通会员买书打八折");
    }
}

class SuperVIP extends Custom {
    private String name = "我是SVIP";
    public void buyBook() {
        System.out.println("超级会员买书打六折");
    }
    public String getName() {
        return name;
    }
}

class Owner {
    private Custom custom;

    public Custom getCustom() {
        return custom;
    }

    public void setCustom(Custom custom) {
        this.custom = custom;
    }

    public void settlement() {
        custom.buyBook();
    }
}

抽象方法和抽象类

  • 抽象类中可以有普通方法,普通类中不能有抽象方法
  • 当子类继承抽象父类,若子类不是抽象类,则要重写父类的所有抽象方法

简单概括以下包装器类的作用

对应8个基本数据类型有8个包装器类,这些类可以用于新建对应8个基本数据类型的对象,并且有着自动装箱,拆箱的功能(如:很多时候一些方法的参数时Obj类型的对象,但是我们直接传入基本数据类型的参数可以完成调用),在这些类中还定义了许多静态方法用于基本数据类型和包装器类之间的转换,这里感觉力扣上刷点题应该就会熟悉这些API了

面向接口编程时的一些细节

有一种说法:接口其实就是抽象类,极度抽象的抽象类,接口中不能存在非抽象方法,接口中的所有方法必须全部是抽象方法。在接口使用时有以下一些要求:

  • 接口中成员变量只能定义public和默认访问权限修饰符
  • 接口中的成员变量默认为static final类型,可以通过接口直接访问,同时不能修改值,必须在声明时完成初始化
  • Java支持单继承,但是可以实现多个接口,这里就体现了接口的解耦合以及制定规范的作用

final关键字的功能

  • 修饰一个方法:该方法可以被继承但无法被重写

  • 修饰一个类:该类为最终类,无法被继承

  • 修饰一个基本数据类型:该值为常量无法被修改

以byte类型为例解释计算机中以补码存储的细节

在八位二进制下,原码左侧第一位为符号位,除符号位外其余位取反为反码,反码+1为补码,计算机中以补码形式存储数据,其中要注意0的存储与-128的存储

  • 0:由于以补码形式存储,0不分正负,这里将0000 0000作为0的补码
  • -128:-128没有原码与反码,而原本“-0”的补码1000 0000用于表示-128的补码,这样就充分利用了每一个补码的表示
  • 补码的意义:举个栗子,1按照0000 0001的补码存储,-1按照1111 1111的补码存储,-1+1 = 0 <=> 0000 0001 + 1111 1111,补码相加后溢出正好为0,符合-1+1 == 0的结果,这就正好自圆其说了

下面是一个byte型的1左移6位,7位,8位后的结果测试:

public class Test1 {
    public static void main(String[] args) {
        byte test = 1; test <<= 6;
        System.out.println(test);
        test = 1; test <<= 7;
        System.out.println(test);
        test = 1; test <<= 8;
        System.out.println(test);
		//以下两个是int左移7位和31位,这里是用于区分的,因为默认是int
        System.out.println(1<<7);
        System.out.println(1<<31);
    }
}

异常类的分类&捕获与throw & throws的区分

异常类是一个树形的族谱结构,以下两个异常为Throwable类的两个子类

  • Error:系统错误,程序无法处理
  • Exception:程序运行时出现的错误,程序员可以处理()
try {
	//可能会抛出异常的代码
} catch {
	//对异常进行处理	
} finally {
	//一定会执行的代码
}

throws与throw的用法原文出处

throws关键字的作用:

  • throws作用于方法,修饰符 返回值类型 方法名(参数列表) throws Exception
  • throw是写在逻辑处理过程中的代码,人为抛出异常
  • 如果方法 throw 的是 RuntimeException 异常或者其子类,外部调用时可以不处理,JVM 会处理。
  • 当人为抛出Exception对象(或者其子类异常对象时),我们必须处理这个异常对象(在本方法内对抛出的异常进行捕获操作),但是也可以选择自己不捕获,试图甩锅?这时可以用throws修饰这个方法,这样throws就会将异常对象抛出给方法的调用者处理(自己不处理给别人处理),此时方法的调用者就可以选择try-catch进行捕获,或者依旧将自己用throws修饰,继续甩锅?最终如果main方法不处理就会交给JVM处理(中断程序)

接下来时三种throws的使用情况

1.下面的代码是异常抛出方法本身不处理,并且调用者依旧选择不处理,将其交给JVM中断程序处理

public class Test1 {
    
    public static void main(String[] args) throws Exception {
        int[] array = {1, 2, 3};
        //此时main方法中并未对这个抛出异常对象的方法进行捕获操作,选择交给JVM中断处理程序
        test(array, -1);
    }

    public static void test(int[] array, int index) throws Exception {
        if (index < 0 || index >= 3)
            //这句抛出就必须处理这个异常对象,此时在方法后用throws相当于
            //将这个抛出的异常交给方法的调用者main方法处理
            throw new Exception("下标越界");
        System.out.println(array[index]);
    }
}

2.下面代码是test方法不处理,调用者main方法选择自己try-catch捕获抛出的异常,自己处理

public class Test1 {
    public static void main(String[] args) {
        int[] array = {1, 2, 3};
        //此时方法调用者main选择捕获这个抛出异常的方法
        try {
            test(array, -1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void test(int[] array, int index) throws Exception {
        if (index < 0 || index >= 3)
            //这句抛出就必须处理这个异常对象,此时在方法后用throws相当于
            //将这个抛出的异常交给方法的调用者main方法处理
            throw new Exception("下标越界");
        System.out.println(array[index]);
    }
}

3.下面的代码时test方法选择自己直接捕获自己抛出的异常,并进行处理

public class Test1 {

    public static void main(String[] args) {
        int[] array = {1, 2, 3};
        test(array, -1);
    }

    public static void test(int[] array, int index) {
        if (index < 0 || index >= 3)
            //此时test本方法直接自己处理自己抛出的异常对象
            try {
                throw new Exception("下标越界");
            } catch (Exception e) {
                e.printStackTrace();
            }
        System.out.println(array[index]);
    }
}

Java中开启线程的两种方式

  • 通过继承Thread类,Thread类本身继承了Runnable接口,所以内部已经有了run方法,需要重写run方法为我们需要的业务逻辑代码,之后实例化Thread类的实例后调用start方法,而不是run方法(start方法包含了开启线程,等待资源分配,然后调用run方法的过程,直接调用run方法就是单线程的普通调用方法的过程)
  • 通过实现Runnable接口,实现该接口的run方法为我们需要的业务逻辑,由于单单Runnable的实现类是没有开启线程的能力的,需要有Thread类的实例来开启线程。综上:利用Thread的构造函数,以Runnable接口的实现类为参数,声明初始化一个Thread类(要明确线程类是用于抢占CPU资源的,而抢占到资源后就会执行run方法中的任务,这里的Runnable接口的实现类就是run方法的承载者)

下面是一个例子实现了两种不同的开启线程的方式

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

        //通过实现接口的方式可以解耦合
        MyRunnable myRunnable = new MyRunnable();
        //调用Thread类的构造函数传入线程需要执行的任务(业务逻辑)
        Thread thread = new Thread(myRunnable);
        //开启线程等待资源分配,然后执行run方法业务
        thread.start();

        //通过继承的方式
        MyThread myThread = new MyThread();
        myThread.start();

        for (int i = 0; i < 1000; i++)
            System.out.println("++++++++++主线程++++++++");
    }
}

class MyRunnable implements Runnable {
    //Runnable是一个接口,内部只有一个抽象的run方法(代表开启线程后需要执行的业务逻辑)
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("--------MyRunnable--------");
        }
    }
}

class MyThread extends Thread {
    //通过继承Thread类的方式,由于Thread类本身就继承了Runnable接口
    // 因此所创建的Thread类的实例内部有run方法
    //这里是重写父类的run方法,替换为自己想要执行的任务
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++)
            System.out.println("=========MyThread============");
    }
}

线程的五种状态以及状态之间的转换

  • 创建状态:实例化一个新的线程对象,还未启动
  • 就绪状态:创建好的线程对象调用start方法完成启动,进入线程池等待抢占CPU资源
  • 运行状态:线程对象获取了CPU资源,在一定的时间内执行任务
  • 阻塞状态:正在运行的线程暂停执行任务,释放占用CPU的资源,并在解除阻塞态之后也不能直接回到运行状态,而是重新回到就绪状态,等待获取CPU资源
  • 终止状态:线程运行完毕或者因为异常导致程序终止运行
创建——》就绪、就绪《——》运行、运行——》阻塞、阻塞——》就绪、运行——》终止

线程的调度:休眠&合并&礼让&中断

  • 线程休眠:让当前线程暂停执行,从运行状态进入阻塞状态(相当于被挂起一定的时间,且还不允许回到就绪状态),将CPU资源释放,通过sleep()实现,sleep()是Thread类的静态本地方法,具体实现用c/c++,对于任何一个线程的实例对象来说(包括主线程),都可以使用Thread.sleep()的静态方法实现对当前线程的挂起,但如果该对象已经继承了Thread类,则可以直接打点调用sleep()方法

注意点:在外部调用时,sleep休眠要放在启动之前;否则就是在内部调用sleep则可以随时休眠,下面是一个小的例子

public class Test1 {
    public static void main(String[] args) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //下面开启子线程的代码也是包含在main主线程中的,所以主线程挂起后
        //下面的代码也无法继续执行
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++)
            System.out.println("-----MyRunnable------");
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++)
            System.out.println("++++++++MyThread+++++++++");
    }
}

  • 线程合并:线程甲和线程乙,线程甲执行到某个时间点的时候调用线程乙.join方法,则表示从当前时间点开始,CPU资源被线程乙独占,线程甲进入阻塞状态,直到线程乙执行完毕,线程甲进入就绪状态(相当于乙的执行结束时解除甲的阻塞状态的条件),等待获取CPU资源进入运行状态,并且也可以通过给join方法输入参数表示线程乙将独占CPU的时间(不输入参数则是始终独占直到乙线程执行结束)

这里我做了一个测试,如果由三个线程:主线程、甲线程、乙线程,在主线程中某个位置调用甲线程.join(),会使得除主线程外的另外两个线程继续争夺CPU的资源,直到甲结束,主线程才会回到就绪态去竞争CPU的使用权,如果此时乙线程还没有结束(我估计在测试代码中将乙线程的循环写的很长),则会继续与主线程进行CPU资源的竞争

public class Test1 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();

        MyThread myThread = new MyThread();
        myThread.start();

        for (int i = 0; i < 100; i++) {
            System.out.println(i + "========main========");
            if (i == 50) {
                try {
                    Thread.sleep(2000);
                    myThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 1000000; i++)
            System.out.println(i + "-----MyRunnable------");
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++)
            System.out.println(i + "++++++++MyThread+++++++++");
    }
}
  • 线程礼让:yield是一个静态方法,通过调用该方法,是在某个特定的时间点让线程暂停抢占CPU资源的行为,只是暂时的礼让

  • 线程中断:很多情况导致线程停止运行,如线程执行完毕自动停止,线程执行过程中遇到错误抛出异常并停止,或者线程执行过程中根据需求手动停止,有如下三个实例方法

    • public void stop() 强行中断线程,不推荐使用
    • public void interrupt() 中断当前线程
    • public boolean isInterrupted() true表示已经中断,而false表示未中断(当然如果线程处于没有运行的就绪态那就称不上中断)
  • 一些获取当前线程信息的实例方法:

    • thread.setName("线程一") 为当前线程设置一个名称
    • thread.getState() 获取当前线程的状态,是一个枚举类型

匿名内部类实现接口或者继承父类

new SuperType(construction parameters) {
	inner class methods and data
}

其中,SuperType可以是接口,如果是这样,内部类就要实现这个接口,Super Type也可以是一个类,如果是这样,内部类就要扩展这个类。

posted on 2021-01-19 21:00  白泽talk  阅读(153)  评论(0编辑  收藏  举报