Java面向对象
基本类型包装类
包装类介绍
Java提供的基本类型包装类,使得Java能够更好的体现面向对象的思想,同时也使得基本类型能够支持对象操作
包装类实际上就是将我们的基本数据类型,封装成一个类(运用了封装的思想)
自动装箱/拆箱:
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” 中的所有 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)之外的任何单个字符,相等于 [^\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
语句块至少要配合catch
或finally
中的一个
断言表达式
可以使用断言表达式来对某些东西进行判断,如果判断失败会抛出错误,只不过默认情况下没有开启断言,我们需要在虚拟机参数中手动开启一下:
断言表达式需要使用到assert
关键字,如果assert后面的表达式判断结果为false,将抛出AssertionError错误。
可以在表达式的后面添加错误信息:
public static void main(String[] args) { int a = 10; assert a > 10 : "我是自定义的错误信息"; }
常用工具类介绍
数学工具类
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来进行深层次判断 }