Java面向对象进阶

2023-10-23 09:53:20 浏览数 (1)

Java面向对象

基本类型包装类

包装类介绍

Java提供的基本类型包装类,使得Java能够更好的体现面向对象的思想,同时也使得基本类型能够支持对象操作

包装类实际上就是将我们的基本数据类型,封装成一个类(运用了封装的思想)

自动装箱/拆箱:

代码语言:javascript复制
Integer i = 10;
    int a = i;

本质上就是:

代码语言:javascript复制
Integer i =  Integer.valueOf(10);//自动装箱
    int a = i.intValue();  //自动拆箱

包装类对象比较:

代码语言:javascript复制
public static void main(String[] args) {
    Integer a = new Integer(10);
    Integer b = new Integer(10);
    System.out.println(a == b);    //虽然a和b的值相同,但是并不是同一个对象,所以说==判断为假
}
代码语言:javascript复制
public static void main(String[] args) {
    Integer a = 10, b = 10;
    System.out.println(a == b);//true
}

通过自动装箱转换的Integer对象,如果值相同,得到的会是同一个对象:IntegerCache会默认缓存-128~127之间的所有值,将这些值提前做成包装类放在数组中存放,这是为了提升效率,因为小的数使用频率非常高,有些时候并不需要创建那么多对象,创建对象越多,内存也会消耗更多。同样的,Long、Short、Byte类型的包装类也有类似的机制。

字符串常用方法:

代码语言:javascript复制
Integer i = Integer.valueOf("5555");//字符串转Integer
Integer i = Integer.decode("0xA6");//十六进制和八进制的字符串进行解码
 System.out.println(Integer.toHexString(166));//十进制的整数转换为8进制
特殊包装类

用于计算超大数字的BigInteger

代码语言:javascript复制
BigInteger i = BigInteger.valueOf(Long.MAX_VALUE);
i = i.multiply(BigInteger.valueOf(Long.MAX_VALUE));   //即使是long的最大值乘以long的最大值,也能给你算出来
i = i.pow(100);   //long的最大值来个100次方吧
i = i.divide(BigDecimal.valueOf(3), 100, RoundingMode.CEILING);
//计算10/3的结果,精确到小数点后100位
//RoundingMode是舍入模式,就是精确到最后一位时,该怎么处理,这里CEILING表示向上取整

数组

一维数组

数组是相同类型数据的有序集合,数组可以代表任何相同类型的一组内容(包括引用类型和基本类型)其中存放的每一个数据称为数组的一个元素

数组类型比较特殊,它本身也是类,但是编程不可见(底层C 写的,在运行时动态创建)即使是基本类型的数组,也是以对象的形式存在的,并不是基本数据类型。

代码语言:javascript复制
public static void main(String[] args) {
    int[] array = new int[10];   //在创建数组时,需要指定数组长度,也就是可以容纳多个int变量的值
  	Object obj = array;   //因为同样是类,肯定是继承自Object的,所以说可以直接向上转型
}

数组每个位置上都有默认值,如果是引用类型,就是null,如果是基本数据类型,就是0,或者是false

数组的下标是从0开始的,不是从1开始的

代码语言:javascript复制
int[] array = new int[10];
array[0] = 888;   //就像使用变量一样,是可以放在赋值运算符左边的,我们可以直接给对应下标位置的元素赋值

数组本身也是一个对象,数组对象也是具有属性的

代码语言:javascript复制
int[] array = new int[10];
System.out.println("当前数组长度为:" array.length);   //length属性是int类型的值,表示当前数组长度,长度是在一开始创建数组的时候就确定好的

由于基本数据类型和引用类型不同,所以说int类型的数组时不能被Object类型的数组变量接收的;如果是引用类型的话,是可以的

代码语言:javascript复制
String[] arr = new String[10];
Object[] array = arr;    //数组同样支持向上转型	
Object[] arr = new Object[10];
String[] array = (String[]) arr;   //也支持向下转型
可变长参数

可变长参数本质就是一个数组:

代码语言:javascript复制
public void test(String... strings){   //strings这个变量就是一个String[]类型的
    for (String string : strings) {
        System.out.println(string);   //遍历打印数组中每一个元素
    }
}

字符串

String类

每个用双引号括起来的字符串,都是String类型的一个实例对象

代码语言:javascript复制
String str = "Hello World!";
String str = new String("Hello World!");  //这种方式就是创建一个新的对象

直接使用双引号创建的字符串,如果内容相同,为了优化效率,那么始终都是同一个对象:

代码语言:javascript复制
String str1 = "Hello World";
String str2 = "Hello World";
System.out.println(str1 == str2);

如果我们使用构造方法主动创建两个新的对象,那么就是不同的对象了:

代码语言:javascript复制
String str1 = new String("Hello World");
String str2 = new String("Hello World");
System.out.println(str1 == str2);

String常用方法:

代码语言:javascript复制
System.out.println(str.length());   //length方法可以求字符串长度,这个长度是字符的数量
//双引号括起来的字符串本身就是一个实例对象
System.out.println("Hello World".length());   //虽然看起来挺奇怪的,但是确实支持这种写法
String sub = str.substring(0, 3);   //分割字符串,并返回一个新的子串对象
String[] strings = str.split(" ");   //使用split方法进行字符串分割,比如这里就是通过空格分隔,得到一个字符串数组
char[] chars = str.toCharArray();  //字符数组和字符串之间转换
char[] chars = new char[]{'奥', '利', '给'};
String str = new String(chars);
StringBuilder

StringBuilder类型,实际上是专门用于构造字符串的,我们可以使用它来对字符串进行拼接、裁剪等操作,弥补了字符串不能修改的不足:

代码语言:javascript复制
StringBuilder builder = new StringBuilder();   //一开始创建时,内部什么都没有
builder.append("AAA");   //我们可以使用append方法来讲字符串拼接到后面
builder.append("BBB");
System.out.println(builder.toString());   //当我们字符串编辑完成之后,就可以使用toString转换为字符串了
builder.delete(2, 4);   //删除2到4这个范围内的字符
正则表达式

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等

限定符表如下:

字符

描述

*

匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。***** 等价于 {0,}。

匹配前面的子表达式一次或多次。例如,zo 能匹配 “zo” 以及 "zoo",但不能匹配 “z”。 等价于 {1,}。

?

匹配前面的子表达式零次或一次。例如,do(es)? 可以匹配 “do” 、 “does”、 “doxy” 中的 “do” 。? 等价于 {0,1}。

{n}

n 是一个非负整数。匹配确定的 n 次。例如,o{2} 不能匹配 “Bob” 中的 o,但是能匹配 “food” 中的两个 o。

{n,}

n 是一个非负整数。至少匹配n 次。例如,o{2,} 不能匹配 “Bob” 中的 o,但能匹配 “foooood” 中的所有 o。o{1,} 等价于 o 。o{0,} 则等价于 o*。

{n,m}

m 和 n 均为非负整数,其中 n <= m。最少匹配 n 次且最多匹配 m 次。例如,o{1,3} 将匹配 “fooooood” 中的前三个 o。o{0,1} 等价于 o?。请注意在逗号和两个数之间不能有空格。

多种字符匹配:

字符

描述

[ABC]

匹配 […] 中的所有字符,例如 [aeiou] 匹配字符串 “google runoob taobao” 中所有的 e o u a 字母。

[^ABC]

匹配除了 […] 中字符的所有字符,例如 [^aeiou] 匹配字符串 “google runoob taobao” 中除了 e o u a 字母的所有字母。

[A-Z]

[A-Z] 表示一个区间,匹配所有大写字母,[a-z] 表示所有小写字母。

.

匹配除换行符(n、r)之外的任何单个字符,相等于 [^nr]

[sS]

匹配所有。s 是匹配所有空白符,包括换行,S 非空白符,不包括换行。

w

匹配字母、数字、下划线。等价于 [A-Za-z0-9_]

内部类

成员内部类

成员内部类和成员方法、成员变量一样,是对象所有的,而不是类所有的

成员内部类也可以使用访问权限控制

代码语言:javascript复制
public class Test {
    public class Inner {   //内部类也是类,所以说里面也可以有成员变量、方法等,甚至还可以继续套娃一个成员内部类
        public void test(){
            System.out.println("我是成员内部类!");
        }
    }
}
public static void main(String[] args) {
    Test test = new Test();
    Test.Inner inner = test.new Inner();
    inner.test();
}

在成员内部类中,是可以访问到外层的变量的:

代码语言:javascript复制
public class Test {
    private final String name;
    
    public Test(String name){
        this.name = name;
    }
    public class Inner {
        public void test(){
            System.out.println("我是成员内部类:" name);
         		//成员内部类可以访问到外部的成员变量
          	//因为成员内部类本身就是某个对象所有的,每个对象都有这样的一个类定义,这里的name是其所依附对象的
        }
    }
}
静态内部类

静态内部类就像静态方法和静态变量一样,是属于类的

不需要依附任何对象,我们可以直接创建静态内部类的对象

静态内部类由于是静态的,所以相对外部来说,整个内部类中都处于静态上下文(注意只是相当于外部来说)是无法访问到外部类的非静态内容的

代码语言:javascript复制
public class Test {
    private final String name;

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

    public static class Inner {
        public void test(){
            System.out.println("我是静态内部类!");
        }
    }
}
局部内部类

局部内部类就像局部变量一样,可以在方法中定义。

既然是在方法中声明的类,那作用范围也就只能在方法中了。

代码语言:javascript复制
public class Test {
    public void hello(){
        class Inner{   //局部内部类跟局部变量一样,先声明后使用
            public void test(){
                System.out.println("我是局部内部类");
            }
        }
        
        Inner inner = new Inner();   //局部内部类直接使用类名就行
        inner.test();
    }
}
匿名内部类

匿名内部类是我们使用频率非常高的一种内部类,它是局部内部类的简化版。

在抽象类和接口中都会含有某些抽象方法需要子类去实现,不能直接通过new的方式去创建一个抽象类或是接口对象,但是我们可以使用匿名内部类。

在方法中使用匿名内部类,将其中的抽象方法实现,并直接创建实例对象。

代码语言:javascript复制
public static void main(String[] args) {
    Student student = new Student() {   //在new的时候,后面加上花括号,把未实现的方法实现了
        @Override
        public void test() {
            System.out.println("我是匿名内部类的实现!");
        }
    };
    student.test();
}

匿名内部类中同样可以使用类中的属性(因为它本质上就相当于是对应类型的子类)

接口也可以通过这种匿名内部类的形式,直接创建一个匿名的接口实现类

代码语言:javascript复制
public static void main(String[] args) {
    Study study = new Study() {
        @Override
        public void study() {
            System.out.println("我是学习方法!");
        }
    };
    study.study();
}
Lambda表达式

如果一个接口中有且只有一个待实现的抽象方法,那么我们可以将匿名内部类简写为Lambda表达式

代码语言:javascript复制
public static void main(String[] args) {
    Study study = () -> System.out.println("我是学习方法!");   //是不是感觉非常简洁!
  	study.study();
}

Lambda表达式的具体规范:

  • 标准格式为:([参数类型 参数名称,]...) ‐> { 代码语句,包括返回值 }
  • 和匿名内部类不同,Lambda仅支持接口,不支持抽象类
  • 接口内部必须有且仅有一个抽象方法(可以有多个方法,但是必须保证其他方法有默认实现,必须留一个抽象方法出来)
方法引用

方法引用就是将一个已实现的方法,直接作为接口中抽象方法的实现(当然前提是方法定义得一样才行)

代码语言:javascript复制
public interface Study {
    int sum(int a, int b);   //待实现的求和方法
}
public static void main(String[] args) {
    Study study = (a, b) -> a   b;
}

Integer.sum的参数和返回值,跟我们在Study中定义的完全一样,所以说我们可以直接使用方法引用:

代码语言:javascript复制
public static void main(String[] args) {
    Study study = Integer::sum;    //使用双冒号来进行方法引用,静态方法使用 类名::方法名 的形式
    System.out.println(study.sum(10, 20));
}

如果是普通从成员方法,我们同样需要使用对象来进行方法引用:

代码语言:javascript复制
public static void main(String[] args) {
    Main main = new Main();
    Study study = main::lbwnb;   //成员方法因为需要具体对象使用,所以说只能使用 对象::方法名 的形式
}

public String lbwnb(){
    return "卡布奇诺今犹在,不见当年倒茶人。";
}
代码语言:javascript复制
public static void main(String[] args) {
    Study study = String::new;    //没错,构造方法也可以被引用,使用new表示
}

异常机制

程序运行出现我们没有考虑到的情况时,就有可能出现异常或是错误

异常的类型

每一个异常也是一个类,他们都继承自Exception

异常类型本质依然类的对象,但是异常类型支持在程序运行出现问题时抛出也可以提前声明,告知使用者需要处理可能会出现的异常

运行时异常:在编译阶段无法感知代码是否会出现问题,只有在运行的时候才知道会不会出错

编译时异常:编译时异常明确指出可能会出现的异常,在编译阶段就需要进行处理(捕获异常)必须要考虑到出现异常的情况

还有一种类型是错误,错误比异常更严重:比如OutOfMemoryError就是内存溢出错误

自定义异常

异常其实就两大类,一个是编译时异常,一个是运行时异常

编译时异常只需要继承Exception就行了

运行时异常只需要继承RuntimeException就行了

还有一种类型是Error,它是所有错误的父类,同样是继承自Throwable的

抛出异常

手动抛出一个异常来终止程序继续运行下去,同时告知上一级方法执行出现了问题

代码语言:javascript复制
public static int test(int a, int b) {
    if(b == 0)
        throw new RuntimeException("被除数不能为0");  //使用throw关键字来抛出异常
    return a / b;
}

异常对象携带了我们抛出异常时的一些信息,比如是因为什么原因导致的异常,在RuntimeException的构造方法中我们可以写入原因

如果我们在方法中抛出了一个非运行时异常,那么必须告知函数的调用方我们会抛出某个异常,函数调用方必须要对抛出的这个异常进行对应的处理才可以

异常的处理

出现异常时默认会交给JVM来处理,JVM发现任何异常都会立即终止程序运行,并在控制台打印栈追踪信息

己处理出现的问题,让程序继续运行下去,就需要对异常进行捕获

将代码编写到try语句块中,只要是在这个范围内发生的异常,都可以被捕获,使用catch关键字对指定的异常进行捕获

catch中捕获的类型只能是Throwable的子类,也就是说要么是抛出的异常,要么是错误,不能是其他的任何类型

代码语言:javascript复制
public static void main(String[] args) {
    try {    //使用try-catch语句进行异常捕获
        Object object = null;
        object.toString();
    } catch (NullPointerException e){   //因为异常本身也是一个对象,catch中实际上就是用一个局部变量去接收异常

    }
    System.out.println("程序继续正常运行!");
}

如果某个方法明确指出会抛出哪些异常,除非抛出的异常是一个运行时异常,否则我们必须要使用try-catch语句块进行异常的捕获,不然就无法通过编译

如果我们确实不想在当前这个方法中进行处理,那么我们可以抛给上一级

代码语言:javascript复制
public static void main(String[] args) throws IOException {  //继续编写throws往上一级抛
    test(10);
}
private static void test(int a) throws IOException {
    throw new IOException();
}

如果已经是主方法了,那么就相当于到顶层了,此时发生异常再往上抛出的话,就会直接交给JVM进行处理,默认会让整个程序终止并打印栈追踪信息。

当代码可能出现多种类型的异常时,我们希望能够分不同情况处理不同类型的异常,就可以使用多重异常捕获:

代码语言:javascript复制
try {
  //....
} catch (RuntimeException e){  //父类型在前,会将子类的也捕获

} catch (NullPointerException e) {   //永远都不会被捕获

} catch (IndexOutOfBoundsException e){   //永远都不会被捕获

}

程序运行时,无论是否出现异常,都会在最后执行任务,可以交给finally语句块来处理:

代码语言:javascript复制
try {
    //....
}catch (Exception e){
            
}finally {
  	System.out.println("lbwnb");   //无论是否出现异常,都会在最后执行
}

try语句块至少要配合catchfinally中的一个

断言表达式

可以使用断言表达式来对某些东西进行判断,如果判断失败会抛出错误,只不过默认情况下没有开启断言,我们需要在虚拟机参数中手动开启一下:

断言表达式需要使用到assert关键字,如果assert后面的表达式判断结果为false,将抛出AssertionError错误。

可以在表达式的后面添加错误信息:

代码语言:javascript复制
public static void main(String[] args) {
    int a = 10;
    assert a > 10 : "我是自定义的错误信息";
}

常用工具类介绍

数学工具类
代码语言:javascript复制
public static void main(String[] args) {
  	//Math也是java.lang包下的类,所以说默认就可以直接使用
    System.out.println(Math.pow(5, 3));   //我们可以使用pow方法直接计算a的b次方
  
  	Math.abs(-1);    //abs方法可以求绝对值
  	Math.max(19, 20);    //快速取最大值
  	Math.min(2, 4);   //快速取最小值
  	Math.sqrt(9);    //求一个数的算术平方根
    Math.sin(Math.PI / 2);     //求π/2的正弦值,这里我们可以使用预置的PI进行计算
    Math.cos(Math.PI);       //求π的余弦值
    Math.tan(Math.PI / 4);    //求π/4的正切值

    Math.asin(1);     //三角函数的反函数也是有的,这里是求arcsin1的值
    Math.acos(1);
    Math.atan(0);
    Math.log(Math.E);    //e为底的对数函数,其实就是ln,我们可以直接使用Math中定义好的e
    Math.log10(100);     //10为底的对数函数
    //利用换底公式,我们可以弄出来任何我们想求的对数函数
    double a = Math.log(4) / Math.log(2);   //这里是求以2为底4的对数,log(2)4 = ln4 / ln2
   	ath.ceil(4.5);    //通过使用ceil来向上取整
    Math.floor(5.6);   //通过使用floor来向下取整
}

随机数的生成:

代码语言:javascript复制
public static void main(String[] args) {
    Random random = new Random();   //创建Random对象
    for (int i = 0; i < 30; i  ) {
        System.out.print(random.nextInt(100) " ");  //nextInt方法可以指定创建0 - x之内的随机数
    }
}
数组工具类

打印数组,可以直接通过toString方法转换字符串:

代码语言:javascript复制
public static void main(String[] args) {
    int[] arr = new int[]{1, 4, 5, 8, 2, 0, 9, 7, 3, 6};
    System.out.println(Arrays.toString(arr));
}

支持将数组进行排序:

代码语言:javascript复制
public static void main(String[] args) {
    int[] arr = new int[]{1, 4, 5, 8, 2, 0, 9, 7, 3, 6};
    Arrays.sort(arr);    //可以对数组进行排序,将所有的元素按照从小到大的顺序排放
    System.out.println(Arrays.toString(arr));
}

数组中的内容也可以快速进行填充:

代码语言:javascript复制
public static void main(String[] args) {
    int[] arr = new int[10];
    Arrays.fill(arr, 66);
    System.out.println(Arrays.toString(arr));
}

快速地对一个数组进行拷贝:

代码语言:javascript复制
public static void main(String[] args) {
    int[] arr = new int[]{1, 2, 3, 4, 5};
    int[] target = Arrays.copyOfRange(arr, 3, 5);   //也可以只拷贝某个范围内的内容
    System.out.println(Arrays.toString(target));
    System.out.println(arr == target);
}

将一个数组中的内容拷贝到其他数组中:

代码语言:javascript复制
public static void main(String[] args) {
    int[] arr = new int[]{1, 2, 3, 4, 5};
    int[] target = new int[10];
    System.arraycopy(arr, 0, target, 0, 5);   //使用System.arraycopy进行搬运
    System.out.println(Arrays.toString(target));
}

有序的数可以使用二分搜索:

代码语言:javascript复制
public static void main(String[] args) {
    int[] arr = new int[]{1, 2, 3, 4, 5};
    System.out.println(Arrays.binarySearch(arr, 5));   //二分搜索仅适用于有序数组
}

多维数组打印:

代码语言:javascript复制
public static void main(String[] args) {
    int[][] array = new int[][]{{2, 8, 4, 1}, {9, 2, 0, 3}};
    System.out.println(Arrays.deepToString(array));    //deepToString方法可以对多维数组进行打印
}

Arrays也为一维数组和多维数组提供了相等判断的方法:

代码语言:javascript复制
public static void main(String[] args) {
    int[][] a = new int[][]{{2, 8, 4, 1}, {9, 2, 0, 3}};
    int[][] b = new int[][]{{2, 8, 4, 1}, {9, 2, 0, 3}};
    System.out.println(Arrays.equals(a, b));   //equals仅适用于一维数组
    System.out.println(Arrays.deepEquals(a, b));   //对于多维数组,需要使用deepEquals来进行深层次判断
}

binarySearch(arr, 5)); //二分搜索仅适用于有序数组 }

代码语言:javascript复制
多维数组打印:

~~~java
public static void main(String[] args) {
    int[][] array = new int[][]{{2, 8, 4, 1}, {9, 2, 0, 3}};
    System.out.println(Arrays.deepToString(array));    //deepToString方法可以对多维数组进行打印
}

Arrays也为一维数组和多维数组提供了相等判断的方法:

代码语言:javascript复制
public static void main(String[] args) {
    int[][] a = new int[][]{{2, 8, 4, 1}, {9, 2, 0, 3}};
    int[][] b = new int[][]{{2, 8, 4, 1}, {9, 2, 0, 3}};
    System.out.println(Arrays.equals(a, b));   //equals仅适用于一维数组
    System.out.println(Arrays.deepEquals(a, b));   //对于多维数组,需要使用deepEquals来进行深层次判断
}

0 人点赞