JavaSE学习笔记

发布时间 2023-08-22 18:53:09作者: 爱吃玉米!

Java基础

数据类型扩展及面试题讲解

整数拓展: 进制 、 二进制0b 、 十进制 、 八进制0 、 十六进制0x


浮点数拓展:银行业务怎么表示?钱 —— 最好完全避免使用浮点数进行比较
使用 BigDecimal 数学工具类

  • float:有限、离散、舍入误差、大约、接近但不等于
  • double:精度问题

字符拓展:所有的字符本质皆数字。

  • 编码:Unicode 、 2字节 、 0-65535 、 Excel 、 2^16=65536
    • U0000-UFFFF,如‘ \u0061’,就是a

类型转换

低 ----------------------------------------------------------> 高

byte, short, char → int → long → float → double

JavaDoc生成文档

红框内是为了防止中文乱码

Java流程控制

Scanner

实现程序和人的交互

  • 创建对象 Scanner s = new Scanner(System.in)
  • 获取输入的字符串 s.next() | s.nextLine()
  • 判断是否有输入的数据 s.hasNext() | s.hasNextLine()
  • 关闭 s.close()

  • next():
    • 一定要读到有效字符后才可以结束输入
    • 对有效字符之遇到的空白,自动去掉
    • 只有输入有效字符才将其后面输入的空白作为分隔符或结束符
    • 不能得到带有空格的字符串
  • nextLine():
    • 以Enter为结束符,遇到Enter前的所有字符都返回
    • 可以获得空白

输入特定类型的数据

//判断输入是否为整数
Scanner s = new Scanner(System.in);
int i=0;

System.out.println("请输入整数:");
if(s.hasNextInt()){
    i = s.nextInt();
    System.out.println("整数为"+i);
}else{
    System.out.println("输入非整数");
}

scanner在输入的时候,如果是接连两个s.nextXXX()连着用,第一个如果不符合类型,第二个符合,则直接赋值

增强For循环

语法格式:

for(声明语句 : 表达式)
{
    //代码句子
}
//遍历数组的元素
int[] numbers = {10, 20, 30, 40, 50};

for (int x: numbers){
    System.out.println(x);
}

Java方法

方法重载

方法名相同,参数不同,则可以说一个方法是另一个方法的重载

  • 方法名必须相同
  • 方法的参数类型,参数个数不一样
  • 方法的返回类型可以不相同
  • 方法的修饰符可以不相同
  • main方法也可以被重载

可变参数

传递同类型的可变参数给一个方法。在方法生命中,在指定参数类型后加一个省略号(…)

public static void test(double... numbers) {
    for(int i=0;i< numbers.length; i++){
        System.out.println(numbers[i])
    }
}
//调用
test(1,2,3,4,5);
test(new double[]{1,2,3,4,5});

一个方法中只能指定一个可变参数,它必须是方法中的最后一个参数。任何普通的参数必须在它之前声明。

public void test(int x, int... i){
}

★递归

递归包含两个部分

递归头:什么时候不用调用自身方法。如果没有头,将陷入死循环。

递归体:什么时候需要调用自身方法。

Java数组

数组声明&创建

  • 定义/声明数组变量

    dataType[] array;	//首选
    or
    dataType array[];	//效果相同,并非首选,在c、c++中使用
    
  • 创建数组/初始化

    dataType[] array = new dataType[arraySize];
    

三种初始化&Java内存分析

java内存分析

  • :存放new对象和数组;可以被所有的线程共享,不会存放别的对象引用
  • :存放基本变量类型(会包含这个基本类型的具体数值);引用对象的变量(会存放这个引用在堆里面的具体地址)
  • 方法区:可以被所有的线程共享;包含了所有的class和static变量

三种初始化状态

  • 静态初始化(定义之后大小不可改变)

    //基本类型
    int[] a ={1,2,3};
    //引用类型
    Man men = {new Man(1,1), new Man(2,2)};
    
    pulic class Man(int a, int b){
        ...
    }
    
  • 动态初始化

    int[] a = new int[2];
    a[0]=1;
    a[1]=2;
    
  • 数组的默认初始化

    • 数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。

下标越界

  • 数组长度是确定的,一旦被创建,大小不可改变
  • 元素为相同类型,且元素类型可以是基本类型和引用类型
  • 数组变量属于引用类型,数据可以看作对象,数组中的每个元素相当于该对象的成员变量
  • 数组本身就是对象,Java的对象在堆中,因此数组无论保存原始数据还是其他对象类型,数组对象本身是在堆中的

数组下标越界异常:ArrayIndexOutOfBoundsException


数组的使用(进阶)

增强型for循环+数组

int[] a ={1,2,3,4};
for(int array: a){
    System.out.println(array)
}

数组作为方法参数

public static void main(String[] args){
    int[] a ={1,2,3,4};
    printArray(a);
}

public static void printArray(int[] arrays){
    for(int i=0;i<arrays.length;i++){
        System.out.println(arrays[i]+" ")
    }
}

数组作为返回值

public static void main(String[] args){
    int[] a ={1,2,3,4};
    int[] r = reverse(a);
    printArray(r);
}

//反转数组
public static int[] reverse(int[] arrays){
    int[] result = new int[arrays.length];
    for(int i=0, j=result.length-1; i<arrays.length; i++,j--){
        result[j] = arrays[i];
    }
    return result;
}

二维数组

格式:int[][] a = new int[2][5]

//静态初始化
int[][] array = {{1,2},{3,4},{5,6}}

Arrays类

  • 打印数组元素:Arrays.toString(a)
  • 数组排序:Arrays.sort(a),升序
  • 数组赋值:Arrays.fill(a,0)
  • 比较数组:equals()
  • 查找数组元素:binarySearch()(对有序数组进行二分查找)

稀疏数组

处理方式:

  • 记录数组一共有几行几列,有多少个不同值
  • 把具有不同值的元素和行列及值记录在一个小规模的数组中,从而缩小程序的规模(三元组)
public class sparseArray {
    public static void main(String[] args) {
        //1、创建一个二维数组 11*11 0:没有棋子,  1:黑棋子   2:白棋子
        int[][] array1 = new int[11][11];
        array1[1][2] = 1;
        array1[2][3] = 1;
        System.out.println("输出原始的数组");

        for(int[] ints : array1){
            for(int anInt : ints){
                System.out.print(anInt+"\t");
            }
            System.out.println();
        }

        //转换为稀疏数组
        //获取有效值的个数
        int sum = 0;
        for(int i = 0; i < 11;i++){
            for(int j = 0; j < 11; j ++){
                if(array1[i][j] != 0){
                    sum++;
                }
            }
        }
        System.out.println("有效值的个数:"+sum);

        //2、创建一个稀疏数组的数组
        int[][] array2 = new int[sum + 1][3];
        array2[0][0] = 11;
        array2[0][1] = 11;
        array2[0][2] = sum;

        //遍历二维数组,将非0的值存放到稀疏数组中
        int count = 0;
        for(int i =0; i < array1.length;i++){
            for(int j=0; j<array1[i].length;j++){
                if (array1[i][j]!=0){
                    count++;
                    array2[count][0] = i; //第一个位置存行数
                    array2[count][1] = j; //第二个位置往列数
                    array2[count][2] = array1[i][j]; //第三个位置存放元素值
                }
            }
        }

        //输出稀疏数组
        System.out.println("稀疏数组");
        for(int i=0;i<array2.length;i++){
            System.out.println(array2[i][0]+"\t"
                    +array2[i][1]+"\t"
                    +array2[i][2]);
        }

        System.out.println("================");
        //稀疏数组还原
        System.out.println("稀疏数组还原");
        //1、读取稀疏数组值
        int[][] array3 = new int[array2[0][0]][array2[0][1]];

        //2、给其中的元素还原值
        for(int i=1;i < array2.length ; i++){
            array3[array2[i][0]][array2[i][1]] = array2[i][2];
        }
        //3、打印
        for(int[] ints: array3){
            for(int anInt: ints){
                System.out.print(anInt+"\t");
            }
            System.out.println();
        }
    }
}

★Java面向对象OOP

面向对象

面向过程(线性思维)

  • 步骤清晰简单,第一步做什么,第二步做什么……
  • 面对过程适合处理一些较为简单的问题

面向对象

  • 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后才对某个分类下的细节进行面向过程的思索
  • 面向对象适合处理复杂的问题,适合处理需要多人协作的问题!

对于描述复杂的事物,为了从宏观上把握,从整体上合理分析,我们需要使用面向对象的思路来分析整个儿系统。但是,具体到围观操作,仍然需要面向过程的思路去处理。


面向对象的本质:以类的方式组织代码,以对象的形式封装数据

抽象

三大特定:封装、继承、多态


方法

break和return的区别

  • break:跳出switch,结束循环
  • return:结束方法

调用

值传递


引用传递

属性

属性默认初始化

  • 数字: 0 0.0
  • char:u0000
  • boolean:false
  • 引用:null

类与对象的关系

类:抽象的数据类型,是对一类事物整体描述/定义,但是并不能代表某一个具体的事物,如Person类

对象:抽象概念的具体实例,如张三

一个项目应该只存在一个main方法,所以不是所有类里都有main方法

构造器

一个类即使什么都不写,它也会存在一个方法,即为构造方法(快捷键:alt+ins/insert

  • 必须与类名相同
  • 必须没有返回类型,也不写void

构造器的作用:

  • 实例化初始值
    • 使用new关键字,本质是在调用构造方法
    • 用来初始化对象的值
public class Person {
//    一个类什么也不屑,他也会存在一个方法
//    显示的定义构造器
    String name;

//    构造方法,无参构造 aalt+ins-> select none
    public Person(){
        this.name = "rohal";
    }

//    有参构造:一旦定义了有参构造,无参构造就必须显式定义
//    即,必须有上面的Person()方法,否则报错
    public Person(String name){
//        this代表当前对象
        this.name = name;
    }

}

封装

程序设计追求“高内聚,低耦合”。

  • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
  • 低耦合:仅暴漏少量的方法给外部使用

封装(数据的隐藏),通常应该禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。

即,属性私有,get/set,快捷键alt+ins


意义:

  • 提高程序安全性,保护数据
  • 隐藏代码实现细节
  • 统一接口
  • 提高系统可维护性

继承

继承的本质是对一批类的筹项,从而实现对现实世界更好的建模

extends的意思是”扩展“,子类是父类的继承

Java类中只有单继承,没有多继承


继承是类与类之间的一种关系,除此之外,类和类之间的关系还有依赖、组合、聚合等。

继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,extends


object类

java中所有的类都默认继承Object类。

public class Person{}
等价于
public class Person extends Object{}

Super详解

  • super是调用父类的构造方法,必须在子类构造方法的第一个
  • super必须只能出现在子类的方法或构造方法中!
  • super和this不能同时调用构造方法(因为都要放在第一行,会出错)

exp1.

exp2.


VS this:

  • 代表对象不同:
    • this:本身调用者这个对象
    • super:代表父类对象的应用
  • 前提:
    • this:没有继承也可以使用
    • super:只能在继承条件下才可以使用
  • 构造方法:
    • this():默认本类的构造
    • super():默认父类的构造

方法重写

前提:需要有继承关系,子类重写父类的方法!(若子类重写了父类的方法,则调用子类的,否则调用父类的)

  • 方法名必须相同
  • 参数列表必须相同
  • 修饰符:范围可以扩大但不能缩小: public>protected>default>private
  • 抛出的异常:范围可以被缩小但不能扩大:ClassNotFoundException ——> Exception(大)

重写,子类的方法和父类必须要一致;方法体不同!


为什么需要重写?

  • 父类的功能,子类不一定需要,或者不一定满足!
  • Alt + Insert ; override

静态方法:方法的调用只和左边/定义的数据类型有关。


非静态方法:重写,这里的B b = new A()是子类重写了父类的方法


多态

实现程序的动态编译,通过多态可以让程序的可扩展性更强

  • 同一方法可以根据发送对象的不同而采用多种不同的行为方式
  • 一个对象的实际类型是确定的,但是可以指向对象的引用类的有很多(父类、有关系的类)

注意

  • 多态是方法的多态,属性没有多态
  • 父类和子类,有联系 类型转换异常:ClassCastException!
  • 多态存在的条件:
    • 有继承关系
    • 子类重写父类方法 (除static 方法 不属于实例,final 常量,private 私有类)
    • 父类引用指向子类对象 Father f1 = new Son();
public class Person {

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

public class Student extends Person {

    @Override
    public void run() {
        System.out.println("son");
    }

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

public class Application {
    public static void main(String[] args) {
//        一个对象的实际类型是确定的
//        new Student;
//        new Person;

//        可以指向的引用类型就不确定了:父类的引用指向子类
//        Student 能调用的方法都是自己的或者继承父类的
        Student s1 = new Student();
//        Person父类型,可以指向子类,但是不能调用子类独有的方法
        Person s2 = new Student();
        Object s3 = new Student();
        s1.run(); //输出:son
        s2.run(); //输出:son

//       对象能执行哪些方法,主要看对象左边的类型,与右边关系不大!
        s2.eat();//此处报错,子类重写了父类的方法,执行子类的方法
        s1.eat();//输出:eat
    }
}

instanceof:引用类型之间的转换

可以判断两个类之间是否存在父子关系: System.out.println(X instanceof Y); 能不能编译通过取决于X是不是Y的孩子

public class Person {
    public void run(){
        System.out.println("run");
    }
}
public class Student extends Person {
}
public class Teacher extends Person {
}

public class Application {
    public static void main(String[] args) {
        //Object > String
        //Object > Person > Teacher
        //Object > Person > Student
        Object object = new Student();

        System.out.println(object instanceof Student);  //true
        System.out.println(object instanceof Person);   //true
        System.out.println(object instanceof Object);   //true
        System.out.println(object instanceof Teacher);  //false
        System.out.println(object instanceof String);   //false
        
        Person person = new Person();
        System.out.println(person instanceof Student);  //true
        System.out.println(person instanceof Person);   //true
        System.out.println(person instanceof Object);   //true
        System.out.println(person instanceof Teacher);  //false
//        System.out.println(person instanceof String);   //编译报错
        
        Student student = new Student();
        System.out.println(student instanceof Student);  //true
        System.out.println(student instanceof Person);   //true
        System.out.println(student instanceof Object);   //true
//        System.out.println(student instanceof Teacher);  //编译出错
//        System.out.println(student instanceof String);   //编译报错
    }
}

编译看左,运行看右。

  • 子类转换为父类,子类拓展的方法就会消失。向上转型,不用强制转换。
  • 父类转子类,向下转型,强制转换。
  • 方便方法的调用,减少重复的代码!简洁
public class Application {
    public static void main(String[] args) {
        //类型之间的转化:父     子
        //高                 低
        Person obj = new Student();
//        obj.go();//出错,因为go是孩子类 Student 里的方法

        //将这个对象转换为Student类型,就可以使用Student类型的方法(高->低,强制类型转换)
        Student student = (Student) obj;
        student.go();
        //或者
        ((Student)obj).go();
        
        //子类转换为父类,可能丢失自己的本来的一些方法!
        Person person = student;
        person.go();//报错
    }
}

static关键字

静态变量对于类而言在内存中只有一个,所有对象(实例)所共享,当直接使用类去调用得到说明这个变量是静态的。

只执行一次

public void run(){
    go();//非静态可以调用静态方法
}
public static void go(){
}
public static void main(String[] args){
    new Student().run();
    Student.go();
    go();
}

静态代码块只执行一次

静态导入包

import static java.lang.Math.random;

Math.random()

抽象类

abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。

  • 抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类

抽象类,不能使用new关键字来创建对象,它是用来让子类继承的。
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
抽象的抽象:约束

子类继承抽象类,那么就必须要实现抽象类没有实现的筹项方法,否则该子类也要声明为抽象类

//abstract 抽象类本质:类 需extends,just单继承,how多继承->接口
public abstract class Action {
    //约束~有人帮我们实现
    //abstract,抽象方法,只有方法名,没有方法的实现。
    public abstract void doSomething();
}
//抽象类的所有方法,继承了它的子类,都必须要实现它的方法,除非子类也是abstract,则没必要实现
public class A extends Action {

    @Override
    public void doSomething() {

    }
}

接口

普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有!
接口:只有规范!
作用:

  • 约束
  • 定义一些方法,让不同的人实现
  • public abstract
  • public static final
  • 接口不能被实例化,因为接口中没有构造方法
  • 接口可以实现多个,需要implements,必须要重写接口中的方法

接口即规范,定义的是一组规则,体现了现实世界中”如果你是…则必须能…“的思想。如:如果你是汽车,则必须能跑
接口的本质是契约,例如人要遵循的法律

OO的精髓,是对对象的抽象,最能体现这一点的就是接口。

声明类的关键字:class;声明接口的关键字:interface

//interface 定义的关键字,接口都需要有实现类
public interface UserService {
    //接口中的所有定义都是抽象的 public
    void add(String name);
    void update(String name);
    void delete(String name);
    void query(String name);
}
public interface TimeService {
    void timer();
}
//抽象类: extends
//类 可以实现接口 implements 接口
//实现了接口的类,就需要重写接口中的方法

//多继承,利用接口实现多继承
public class UserServiceImpl implements UserService, TimeService{
    @Override
    public void add(String name) {
    }
    @Override
    public void update(String name) {
    }
    @Override
    public void delete(String name) {
    }
    @Override
    public void query(String name) {
    }
    @Override
    public void timer() {
    }
}

内部类

成员内部类

public class Outer {
    private int id = 10;
    public void out(){
        System.out.println("这是外部类的方法");
    }
    public class Inner{
        public void in(){
            System.out.println("这是内部类的方法");
        }
        //获得外部类的私有属性
        public void getID(){
            System.out.println(id);
        }
    }
}

public class Application {
    public static void main(String[] args) {
        Outer outer = new Outer();
        //通过外部类实例化内部类
        Outer.Inner inner = outer.new Inner();
        inner.in();
        inner.getID();
    }
}

静态内部类

public class Outer {
    private static int id = 10;
    public void out(){
        System.out.println("这是外部类的方法");
    }
    //静态内部类无法访问非静态的属性
    public static class Inner{
        public void in(){
            System.out.println("这是内部类的方法");
        }
        //外部类写法的id拿不到了,因为是静态内部类,所以id也应该定义为静态的
        //这里主要是因为加到内存中的顺序不同
        public void getID(){
            System.out.println(id);
        }
    }
}

拓展

public class Outer {

}
//一个java类中可以有多个class类,但是只能有一个public class
class A{

}

局部内部类

public class Outer {

    public void method(){
        class Inner{
            public void in(){

            }
        }
    }
}

匿名内部类

public class Test {
    public static void main(String[] args) {
        //没有名字初始化类,不用将实例保存到变量中
        new Apple().eat();

        UserService userService = new UserService(){
            @Override
            public void hello(){
            }
        };
    }
}
class Apple{
    public void eat(){
        System.out.println("1");
    }
}

interface UserService{
    void hello();
}

异常机制Exception

Error 和 Exception

检查性异常:用户错误或问题引起的异常,程序员无法预见。

运行时异常:可能被程序员避免,可以在编译时被忽略。

错误ERROR:≠异常,是脱离程序员控制的问题,在代码中通常被忽略。如:栈溢出。

异常的超类:java.lang.Throwable

  • Error:java虚拟机生成并抛出,与编写者无关
    • VirtulMachineError:java虚拟机运行错误
    • A WTError
  • Exception:异常,一般由程序逻辑错误引起
    • IOExcetion
    • RuntimeException:运行时异常
      • ArrayIndexOutOfBoundsException:数组下标越界
      • NullPointerException:空指针
      • ArithmeticException:算数异常
      • MissingResourceException:丢失资源
      • ClassNotFoundException:找不到类

捕获和抛出异常

public class Test {
    public static void main(String[] args) {
        int a = 1;
        int b = 0;
        try {  //try监控区域
            System.out.println(a/b);
        }catch(ArithmeticException e){//捕获异常,括号中为异常的类型,最高为Throwable
            System.out.println("程序出现异常,变量b不能为0");
        }finally{//处理善后工作,是否出异常都会执行
            System.out.println("finally");
        }
        
        //finally可以不要,假设IO流或与资源相关的,需要关闭,这些操作可以写在finally里面
    }
}

public static void main(String[] args) {
    int a = 1;
    int b = 0;
    //假设捕获多个异常:从小到大!
    try {  //try监控区域
        System.out.println(a/b);
    }catch(Error e){
        System.out.println("Error");
    }catch (Exception e){
        System.out.println("Exception");
    }catch (Throwable t){//最大的写最后
        System.out.println("Throwable");
    }
    finally{//处理善后工作,是否出异常都会执行
        System.out.println("finally");
    }

}

选中代码,ctry+alt+t,快捷键

try {
    System.out.println(a/b);
} catch (Exception e) {
    e.printStackTrace();//打印错误的栈信息
} finally {
}

public static void main(String[] args) {
        int a = 1;
        int b = 0;
        try {
            new Test().test(1,0);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        }
    }
    //假设这方法中,处理不了这个异常,在方法上抛出异常
    public void test(int a, int b){
        if(b==0){  //throw  throws
            throw new ArithmeticException();//主动抛出异常,一般在方法中使用
        }
    }

自定义异常及经验

用户自定义异常只需继承Exception类即可

步骤:

  • 创建自定义异常类
  • 在方法中通过throw关键字抛出异常对象
  • 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
  • 在出现异常方法的调用者中捕获并处理异常
//自定义异常类
public class MyException extends Exception{
    //传递数字 >10
    private int detail;
    public MyException(int a) { //3
        this.detail = a;
    }
    //toString:异常的打印信息
    @Override
    public String toString() {
        return "MyException{"+detail+'}';//4
    }
}

public class Test {
    //可能会存在异常的方法
    static void test(int a) throws MyException{
        System.out.println("传递的参数为"+a);
        if (a>10){
            throw new MyException(a); //1、抛出异常
        }
        System.out.println("OK");
    }

    public static void main(String[] args) {
        try {
            test(11);
        } catch (MyException e) { //2、捕获异常
            //增加处理异常的代码
            System.out.println("MyException=>"+e);
        }
    }
}

总结:

  • 处理运行异常时,采用逻辑去合理规避同时辅助try-catch处理
  • 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
  • 对于不确定的代码,也可以加上try-catch,处理潜在的异常
  • 尽量去处理异常,要根据不同的业务需求和异常类型去决定
  • 尽量添加finally语句块去释放占用的资源,IO,Scanner
点击查看代码
<details>
<summary>点击查看代码</summary>

</details>