Java面向对象进阶

简介: Java面向对象进阶

Java面向对象


基本类型包装类

包装类介绍

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

e99500af67f2538515a9f2a140247753.png

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

自动装箱/拆箱:

Integer i = 10;
    int a = i;


本质上就是:

Integer i =  Integer.valueOf(10);//自动装箱
    int a = i.intValue();  //自动拆箱


包装类对象比较:

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


public static void main(String[] args) {
    Integer a = 10, b = 10;
    System.out.println(a == b);//true
}

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


字符串常用方法:

Integer i = Integer.valueOf("5555");//字符串转Integer
Integer i = Integer.decode("0xA6");//十六进制和八进制的字符串进行解码
 System.out.println(Integer.toHexString(166));//十进制的整数转换为8进制


特殊包装类

用于计算超大数字的BigInteger

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++写的,在运行时动态创建)即使是基本类型的数组,也是以对象的形式存在的,并不是基本数据类型。

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


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

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

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


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

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


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

String[] arr = new String[10];
Object[] array = arr;    //数组同样支持向上转型 
Object[] arr = new Object[10];
String[] array = (String[]) arr;   //也支持向下转型


可变长参数

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

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


字符串

String类

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

String str = "Hello World!";
String str = new String("Hello World!");  //这种方式就是创建一个新的对象


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

String str1 = "Hello World";
String str2 = "Hello World";
System.out.println(str1 == str2);


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

String str1 = new String("Hello World");
String str2 = new String("Hello World");
System.out.println(str1 == str2);


String常用方法:

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

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” 中的所有 oo{1,} 等价于 o+o{0,} 则等价于 o*
{n,m} m 和 n 均为非负整数,其中 n <= m。最少匹配 n 次且最多匹配 m 次。例如,o{1,3} 将匹配 “fooooood” 中的前三个 oo{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)之外的任何单个字符,相等于 [^\n\r]
[\s\S] 匹配所有。\s 是匹配所有空白符,包括换行,\S 非空白符,不包括换行。
\w 匹配字母、数字、下划线。等价于 [A-Za-z0-9_]


内部类

成员内部类

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

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

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();
}


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

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是其所依附对象的
        }
    }
}


静态内部类

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

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

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

public class Test {
    private final String name;
    public Test(String name){
        this.name = name;
    }
    public static class Inner {
        public void test(){
            System.out.println("我是静态内部类!");
        }
    }
}


局部内部类

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

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

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


匿名内部类

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

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

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

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


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

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

public static void main(String[] args) {
    Study study = new Study() {
        @Override
        public void study() {
            System.out.println("我是学习方法!");
        }
    };
    study.study();
}


Lambda表达式

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

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


Lambda表达式的具体规范:

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


方法引用

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

public interface Study {
    int sum(int a, int b);   //待实现的求和方法
}
public static void main(String[] args) {
    Study study = (a, b) -> a + b;
}


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

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


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

public static void main(String[] args) {
    Main main = new Main();
    Study study = main::lbwnb;   //成员方法因为需要具体对象使用,所以说只能使用 对象::方法名 的形式
}
public String lbwnb(){
    return "卡布奇诺今犹在,不见当年倒茶人。";
}


public static void main(String[] args) {
    Study study = String::new;    //没错,构造方法也可以被引用,使用new表示
}

异常机制

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


异常的类型

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

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

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

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

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


自定义异常

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

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

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

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


抛出异常

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

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的子类,也就是说要么是抛出的异常,要么是错误,不能是其他的任何类型

public static void main(String[] args) {
    try {    //使用try-catch语句进行异常捕获
        Object object = null;
        object.toString();
    } catch (NullPointerException e){   //因为异常本身也是一个对象,catch中实际上就是用一个局部变量去接收异常
    }
    System.out.println("程序继续正常运行!");
}


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

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

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


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

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

try {
  //....
} catch (RuntimeException e){  //父类型在前,会将子类的也捕获
} catch (NullPointerException e) {   //永远都不会被捕获
} catch (IndexOutOfBoundsException e){   //永远都不会被捕获
}


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

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


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


断言表达式

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

4f1f72c1413f590b6e7b8f20accfc592.png

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

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

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


ceb17ed575cf67af9bb96f8d2da84ab2.png


常用工具类介绍

数学工具类

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来向下取整
}


随机数的生成:

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方法转换字符串:

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));
}


支持将数组进行排序:

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));
}


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

public static void main(String[] args) {
    int[] arr = new int[10];
    Arrays.fill(arr, 66);
    System.out.println(Arrays.toString(arr));
}


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

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);
}


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

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));
}


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

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


多维数组打印:

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也为一维数组和多维数组提供了相等判断的方法:

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)); //二分搜索仅适用于有序数组

}

多维数组打印:
~~~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也为一维数组和多维数组提供了相等判断的方法:

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来进行深层次判断
}


相关文章
|
16天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
10 2
|
2月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
2月前
|
Java
java中面向过程和面向对象区别?
java中面向过程和面向对象区别?
33 4
|
2月前
|
Java
接口和抽象类【Java面向对象知识回顾②】
本文讨论了Java中抽象类和接口的概念与区别。抽象类是不能被实例化的类,可以包含抽象和非抽象方法,常用作其他类的基类。接口是一种纯抽象类型,只包含抽象方法和常量,不能被实例化,且实现接口的类必须实现接口中定义的所有方法。文章还比较了抽象类和接口在实现方式、方法类型、成员变量、构造方法和访问修饰符等方面的不同,并探讨了它们的使用场景。
接口和抽象类【Java面向对象知识回顾②】
|
1月前
|
存储 Java 程序员
Java基础-面向对象
Java基础-面向对象
15 0
|
2月前
|
安全 Java Go
面向对象程序设计语言:Java
Java语言语法和C语言和C++语言很接近,很容易学习和使用,Java丢弃了C++中很少使用的、很难理解的、令人迷惑的特性,Java语言不使用指针,而是引用,并提供了自动分配和回收内存空间,使得程序员不必为内存管理而担忧
55 2
|
3月前
|
Java 数据处理 开发者
【Java基础面试十二】、说一说你对面向对象的理解
这篇文章阐述了面向对象是一种以类和对象为基础,通过封装、继承和多态等概念来模拟现实世界中的事物及其相互关系的程序设计方法,它强调以事物为中心进行思考和系统构造,与结构化程序设计相比,更符合人类的自然思维方式。
【Java基础面试十二】、说一说你对面向对象的理解
|
3月前
|
Java
【Java基础面试十三】、面向对象的三大特征是什么?
这篇文章介绍了面向对象程序设计的三大基本特征:封装、继承和多态,其中封装隐藏对象实现细节,继承实现软件复用,多态允许子类对象表现出不同的行为特征。
【Java基础面试十三】、面向对象的三大特征是什么?
|
2月前
|
Java 开发者
Java 面向对象
Java 是一种面向对象的编程语言,通过对象与类的概念组织代码和数据。面向对象编程的核心包括类、对象、继承、多态、封装和抽象。类是对象的蓝图,定义了属性和行为;对象则是类的实例。继承允许子类继承父类的属性和方法,增强代码复用性;多态则支持通过相同接口调用不同类型对象的行为,包括方法重载和重写。封装通过公共方法隐藏对象细节,提高安全性;抽象则对对象特征进行提炼,通过抽象类和接口实现。理解这些概念有助于设计高效、可维护的 Java 应用程序。
|
3月前
|
Java 开发者 C++