第七章 匿名内部类
1. 什么是内部类
在类的内部又定义了一个新的类。被称为内部类。
2. 内部类分类
静态内部类:类似于静态变量
实例内部类:类似于实例变量
局部内部类:类似于局部变量
这里主要讲解一下匿名内部类,其他可参考以下文章
3. 匿名内部类
- 匿名内部类是局部内部类的一种,因为这个类没有名字而得名,叫做匿名内部类。
- 学习匿名内部类主要是让大家以后在阅读别人代码的时候,能够理解。
- 匿名内部类有两个缺点:
缺点1:太复杂,太乱,可读性差。
缺点2:类没有名字,以后想重复使用,不能用。
class Test01{
// 静态变量
static String country;
// 该类在类的内部,所以称为内部类
// 由于前面有static,所以称为“静态内部类”
static class Inner1{
}
// 实例变量
int age;
// 没有static叫做实例内部类。
class Inner2{
}
// 方法
public void doSome(){
// 局部变量
int i = 100;
// 局部内部类。
class Inner3{
}
}
public void doOther(){
// doSome()方法中的局部内部类Inner3,在doOther()中不能用。
}
// main方法,入口
public static void main(String[] args){
// 调用MyMath中的mySum方法。
MyMath mm = new MyMath();
/*
Compute c = new ComputeImpl();
mm.mySum(c, 100, 200);
*/
//合并(这样写代码,表示这个类名是有的。类名是:ComputeImpl)
//mm.mySum(new ComputeImpl(), 100, 200);
// 使用匿名内部类,表示这个ComputeImpl这个类没名字了。
// 这里表面看上去好像是接口可以直接new了,实际上并不是接口可以new了。
// 后面的{} 代表了对接口的实现。
// 不建议使用匿名内部类,为什么?
// 因为一个类没有名字,没有办法重复使用。另外代码太乱,可读性太差。
mm.mySum(new Compute(){
public int sum(int a, int b){
return a + b;
}
}, 200, 300);
}
}
// 负责计算的接口
interface Compute{
// 抽象方法
int sum(int a, int b);
}
// 你自动会在这里编写一个Compute接口的实现类
/*
class ComputeImpl implements Compute{
// 对方法的实现
public int sum(int a, int b){
return a + b;
}
}
*/
// 数学类
class MyMath{
// 数学求和方法
public void mySum(Compute c, int x, int y){
int retValue = c.sum(x, y);
System.out.println(x + "+" + y + "=" + retValue);
}
}
第八章 数组
1. 一维数组
1.1 数组的优点和缺点
1、Java语言中的数组是一种引用数据类型。不属于基本数据类型。数组的父类是Object。
2、数组实际上是一个容器,可以同时容纳多个元素。(数组是一个数据的集合。)
3、数组当中可以存储“基本数据类型”的数据,也可以存储“引用数据类型”的数据。
4、数组因为是引用类型,所以数组对象是堆内存当中。(数组是存储在堆当中的)
5、数组当中如果存储的是“java对象”的话,实际上存储的是对象的“引用(内存地址)”,数组中不能直接存储java对象。
6、数组一旦创建,在java中规定,长度不可变。(数组长度不可变)
7、数组的分类:一维数组、二维数组、三维数组、多维数组…(一维数组较多,二维数组偶尔使用!)
8、所有的数组对象都有length属性(java自带的),用来获取数组中元素的个数。
9、java中的数组要求数组中元素的类型统一。比如int类型数组只能存储int类型,Person类型数组只能存储Person类型。
10、数组在内存方面存储的时候,数组中的元素内存地址(存储的每一个元素都是有规则的挨着排列的)是连续的。内存地址连续。这是数组存储元素的特点(特色)。数组实际上是一种简单的数据结构。
11、所有的数组都是拿“第一个小方框的内存地址”作为整个数组对象的内存地址。(数组中首元素的内存地址作为整个数组对象的内存地址。)
12、数组中每一个元素都是有下标的,下标从0开始,以1递增。最后一个元素的下标是:length - 1下标非常重要,因为我们对数组中元素进行“存取”的时候,都需要通过下标来进行。
13、数组这种数据结构的优点和缺点是什么?
优点:
查询/查找/检索某个下标上的元素时效率极高。可以说是查询效率最高的一个数据结构。
缺点:
第一:由于要保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候, 效率较低,因为随机增删元素会涉及到后面元素统一向前或者向后位移的操作。
第二:数组不能存储大数据量,因为很难在内存空间上找到一块很大的连续的内存空间。
为什么检索效率高? 第一:每一个元素的内存地址在空间存储上是连续的。
第二:每一个元素类型相同,所以占用空间大小一样。
第三:知道第一个元素内存地址,知道每一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率是最高的。数组中存储100个元素,或者存储100万个元素,在元素查询/检索方面,效率是相同的,因为数组中元素查找的时候不会一个一个找,是通过数学表达式计算出来的。(算出一个内存地址,直接定位的。)
注意: 对于数组中最后一个元素的增删,是没有效率影响的。
14、怎么声明/定义一个一维数组?
语法格式:int[] array1;
15、怎么初始化一个一维数组呢?
包括两种方式:静态初始化,动态初始化:
静态初始化语法格式:
int[] array = {100, 2100, 300, 55};
动态初始化语法格式:
int[] array = new int[5]; // 这里的5表示数组的元素个数。
// 初始化一个5个长度的int类型数组,每个元素默认值0
String[] names = new String[6]; // 初始化6个长度的String类型数组,每个元素默认值null。
在这需要注意一个异常:ArrayIndexOutOfBoundsException 什么时候采用静态初始化方式,什么时候使用动态初始化方式呢? 创建数组的时候,确定数组中存储哪些具体的元素时,采用静态初始化方式。 创建数组的时候,不确定将来数组中存储哪些数据,可以采用动态初始化,预先分配内存空间。
1.2 main方法上面的“String[] args”
这个方法程序员负责写出来,JVM负责调用。JVM调用的时候一定会传一个String数组过来。
public class ArrayTest05 {
public static void main(String[] args) {
// JVM默认传递过来的这个数组对象的长度?默认0
// 通过测试得出:args不是null。
System.out.println("JVM给传递过来的String数组参数,它这个数组的长度是?" + args.length);
// 以下这一行代码表示的含义:数组对象创建了,但是数组中没有任何数据。
//String[] strs = new String[0];
//String[] strs = {}; // 静态初始化数组,里面没东西。
//printLength(strs);
// 这个数组什么时候里面会有值呢?
// 其实这个数组是留给用户的,用户可以在控制台上输入参数,这个参数自动会被转换为“String[] args”
// 例如这样运行程序:java ArrayTest05 abc def xyz
// 那么这个时候JVM会自动将“abc def xyz”通过空格的方式进行分离,分离完成之后,自动放到“String[] args”数组当中。
// 所以main方法上面的String[] args数组主要是用来接收用户输入参数的。
// 把abc def xyz 转换成字符串数组:{"abc","def","xyz"}
// 遍历数组
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
public static void printLength(String[] args){
System.out.println(args.length); // 0
}
}
我们使用equals方法时需要考虑一下,引用不可以为空,不然会出现空指针异常
1.3 java中的数组是怎么进行拷贝
public class ArrayTest08 {
public static void main(String[] args) {
// java中的数组是怎么进行拷贝的呢?
//System.arraycopy(5个参数);
// 拷贝源(从这个数组中拷贝)
int[] src = {1, 11, 22, 3, 4};
// 拷贝目标(拷贝到这个目标数组上)
int[] dest = new int[20]; // 动态初始化一个长度为20的数组,每一个元素默认值0
// 调用JDK System类中的arraycopy方法,来完成数组的拷贝
//System.arraycopy(src, 1, dest, 3, 2);
// 遍历目标数组
/*
for (int i = 0; i < dest.length; i++) {
System.out.println(dest[i]); // 0 0 0 11 22 ... 0
}
*/
System.arraycopy(src, 0, dest, 0, src.length);
for (int i = 0; i < dest.length; i++) {
System.out.println(dest[i]);
}
// 数组中如果存储的元素是引用,可以拷贝吗?当然可以。
String[] strs = {"hello", "world!", "study", "java", "oracle", "mysql", "jdbc"};
String[] newStrs = new String[20];
System.arraycopy(strs, 0, newStrs, 0, strs.length);
for (int i = 0; i < newStrs.length; i++) {
System.out.println(newStrs[i]);
}
System.out.println("================================");
Object[] objs = {new Object(), new Object(), new Object()};
Object[] newObjs = new Object[5];
// 思考一下:这里拷贝的时候是拷贝对象,还是拷贝对象的地址。(地址。)
System.arraycopy(objs, 0, newObjs, 0, objs.length);
for (int i = 0; i < newObjs.length; i++) {
System.out.println(newObjs[i]);
}
}
}
拷贝完记得把原数组的引用变为null,便于GC进行垃圾回收
2. 二维数组
二维数组其实是一个特殊的一维数组,特殊在这个一维数组当中的每一个元素是一个一维数组
第八章 Arrays工具类
1. 常见排序算法
1.1 冒泡排序
1、每一次循环结束后,都要找出最大的数,放到参与比较的这堆数据的最右边(冒出最大的那个气泡)
2、核心:拿着左边的数字和右边的数字比对,当左边 > 右边的时候,交换位置
3、冒牌排序算法基本代码:
for(int i = arr.length-1; i > 0; i--){
for(int j = 0; j < i; j++){
// 不管是否需要交换位置,总之是要比较一次的。
//count++;
if(arr[j] > arr[j+1]){
// 交换位置。
// arr[j] 和 arr[j+1] 交换
int temp;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
count2++;
}
}
}
1.2 选择排序
- 每一次从这堆“参与比较的数据当中”找出最小值,拿着这个最小值和“参与比较的这堆最前面的元素”交换位置
- 选择排序比冒泡排序好在:每一次的交换位置都是有意义的
- 关键点:选择排序中的关键在于,你怎么找出一堆数据中最小的
public class SelectSort {
public static void main(String[] args) {
//int[] arr = {3, 1, 6, 2, 5};
int[] arr = {9, 8, 10, 7, 6, 0, 11};
int count = 0;
int count2 = 0;
// 选择排序
// 5条数据循环4次。(外层循环4次。)
for(int i = 0; i < arr.length - 1; i++){
// i的值是0 1 2 3
// i正好是“参加比较的这堆数据中”最左边那个元素的下标。
//System.out.println(i);
// i是一个参与比较的这堆数据中的起点下标。
// 假设起点i下标位置上的元素是最小的。
int min = i;
for(int j = i+1; j < arr.length; j++){
count++;
//System.out.println("===>" + j);
if(arr[j] < arr[min]){
min = j; //最小值的元素下标是j
}
}
// 当i和min相等时,表示最初猜测是对的。
// 当i和min不相等时,表示最初猜测是错的,有比这个元素更小的元素,
// 需要拿着这个更小的元素和最左边的元素交换位置。
if(min != i){
// 表示存在更小的数据
// arr[min] 最小的数据
// arr[i] 最前面的数据
int temp;
temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
count2++;
}
}
// 冒泡排序和选择排序实际上比较的次数没变。
// 交换位置的次数减少了。
System.out.println("比较次数" + count); // 21
System.out.println("交换次数:" + count2); // 5
// 排序之后遍历
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
2. 数组的元素查找
2.1 二分法查找
- 数组元素查找有两种方式:
第一种方式:一个一个挨着找,直到找到为止<br /> 第二种方式:二分法查找(算法),这个效率较高
- 二分法查找算法是基于排序的基础之上(没有排序的数据是无法查找的)
- 二分法查找的终止条件:一直折半,直到中间的那个元素恰好是被查找的元素
public class ArrayUtil {
public static void main(String[] args) {
int[] arr = {100,200,230,235,600,1000,2000,9999};
// 找出arr这个数组中200所在的下标。
// 调用方法
int index = binarySearch(arr, 230);
System.out.println(index == -1 ? "该元素不存在!" : "该元素下标" + index);
}
/**
* 从数组中查找目标元素的下标。
* @param arr 被查找的数组(这个必须是已经排序的。)
* @param dest 目标元素
* @return -1表示该元素不存在,其它表示返回该元素的下标。
*/
public static int binarySearch(int[] arr, int dest) {
// 开始下标
int begin = 0;
// 结束下标
int end = arr.length - 1;
// 开始元素的下标只要在结束元素下标的左边,就有机会继续循环。
while(begin <= end) {
// 中间元素下标
int mid = (begin + end) / 2;
if (arr[mid] == dest) {
return mid;
} else if (arr[mid] < dest) {
// 目标在“中间”的右边
// 开始元素下标需要发生变化(开始元素的下标需要重新赋值)
begin = mid + 1; // 一直增
} else {
// arr[mid] > dest
// 目标在“中间”的左边
// 修改结束元素的下标
end = mid - 1; // 一直减
}
}
return -1;
}
}
3. Arrays工具类使用
- binarySearch和sort方法一般是联合使用的,因为二分法查找建立在已经排序的数组
import java.util.Arrays;
public class ArraysTest {
public static void main(String[] args) {
//1.boolean equals(int[] a,int[] b):判断两个数组是否相等。
int[] arr1 = new int[]{1,2,3,4};
int[] arr2 = new int[]{1,3,2,4};
boolean isEquals = Arrays.equals(arr1, arr2);
System.out.println(isEquals);
//2.String toString(int[] a):输出数组信息。
System.out.println(Arrays.toString(arr1));
//3.void fill(int[] a,int val):将指定值填充到数组之中。
Arrays.fill(arr1,10);
System.out.println(Arrays.toString(arr1));
//4.void sort(int[] a):对数组进行排序。
Arrays.sort(arr2);
System.out.println(Arrays.toString(arr2));
//5.int binarySearch(int[] a,int key)
int[] arr3 = new int[]{-98,-34,2,34,54,66,79,105,210,333};
int index = Arrays.binarySearch(arr3, 210);
if(index >= 0){
System.out.println(index);
}else{
System.out.println("未找到");
}
}
}
第九章 String类
1. String类存储原理
- String类在Java.lang包下
- 在java中随便使用双引号括起来的都是String对象。例如:”abc”,”def”,”hello world!”,这是3个String对象。
- java中规定,双引号括起来的字符串,是不可变的,也就是说”abc”自出生到最终死亡,不可变,不能变成”abcd”,也不能变成”ab”
- 在JDK当中双引号括起来的字符串,例如:”abc” “def”都是直接存储在“方法区”的“字符串常量池”当中的。
- 垃圾回收器是不会释放常量的
1.1 String s1 =”abcdef”和String s3 = new String(“xy”)的区别
1.2 String在堆内存的存储
public class StringTest01 {
public static void main(String[] args) {
// 这两行代码表示底层创建了3个字符串对象,都在字符串常量池当中。
String s1 = "abcdef";
String s2 = "abcdef" + "xy";
// 因为"testString"是一个String字符串对象。只要是对象都能调用方法。
String k ="test";
System.out.println("testString".equals(k)); // 建议使用这种方式,因为这个可以避免空指针异常。
System.out.println(k.equals("testString")); // 存在空指针异常的风险。不建议这样写。
}
}
2. String类中的构造方法
- 第一个:String s = new String(“”);
- 第二个:String s = “”; 最常用
- 第三个:String s = new String(char数组);
- 第四个:String s = new String(char数组,起始下标,长度);
- 第五个:String s = new String(byte数组);
- 第六个:String s = new String(byte数组,起始下标,长度)
public class StringTest04 {
public static void main(String[] args) {
// 创建字符串对象最常用的一种方式
String s1 = "hello world!";
// s1这个变量中保存的是一个内存地址。
// 按说以下应该输出一个地址。
// 但是输出一个字符串,说明String类已经重写了toString()方法。
System.out.println(s1);//hello world!
System.out.println(s1.toString()); //hello world!
// 这里只掌握常用的构造方法。
byte[] bytes = {97, 98, 99}; // 97是a,98是b,99是c
String s2 = new String(bytes);
// 前面说过:输出一个引用的时候,会自动调用toString()方法,默认Object的话,会自动输出对象的内存地址。
// 通过输出结果我们得出一个结论:String类已经重写了toString()方法。
// 输出字符串对象的话,输出的不是对象的内存地址,而是字符串本身。
System.out.println(s2.toString()); //abc
System.out.println(s2); //abc
// String(字节数组,数组元素下标的起始位置,长度)
// 将byte数组中的一部分转换成字符串。
String s3 = new String(bytes, 1, 2);
System.out.println(s3); // bc
// 将char数组全部转换成字符串
char[] chars = {'我','是','中','国','人'};
String s4 = new String(chars);
System.out.println(s4);
// 将char数组的一部分转换成字符串
String s5 = new String(chars, 2, 3);
System.out.println(s5);
String s6 = new String("helloworld!");
System.out.println(s6); //helloworld!
}
}
3. String类当中常用方法
public class StringTest05 {
public static void main(String[] args) {
// String类当中常用方法。
//1(掌握).char charAt(int index)
char c = "中国人".charAt(1); // "中国人"是一个字符串String对象。只要是对象就能“点.”
System.out.println(c); // 国
// 2(了解).int compareTo(String anotherString)
// 字符串之间比较大小不能直接使用 > < ,需要使用compareTo方法。
int result = "abc".compareTo("abc");
System.out.println(result); //0(等于0) 前后一致 10 - 10 = 0
int result2 = "abcd".compareTo("abce");
System.out.println(result2); //-1(小于0) 前小后大 8 - 9 = -1
int result3 = "abce".compareTo("abcd");
System.out.println(result3); // 1(大于0) 前大后小 9 - 8 = 1
// 拿着字符串第一个字母和后面字符串的第一个字母比较,根据排序进行减法。能分胜负就不再比较了。
System.out.println("xyz".compareTo("yxz")); // -1
// 3(掌握).boolean contains(CharSequence s)
// 判断前面的字符串中是否包含后面的子字符串。
System.out.println("HelloWorld.java".contains(".java")); // true
System.out.println("http://www.baidu.com".contains("https://")); // false
// 4(掌握). boolean endsWith(String suffix)
// 判断当前字符串是否以某个子字符串结尾。
System.out.println("test.txt".endsWith(".java")); // false
System.out.println("test.txt".endsWith(".txt")); // true
System.out.println("fdsajklfhdkjlsahfjkdsahjklfdss".endsWith("ss")); // true
// 5(掌握).boolean equals(Object anObject)
// 比较两个字符串必须使用equals方法,不能使用“==”
// equals方法有没有调用compareTo方法? 老版本可以看一下。JDK13中并没有调用compareTo()方法。
// equals只能看出相等不相等。
// compareTo方法可以看出是否相等,并且同时还可以看出谁大谁小。
System.out.println("abc".equals("abc")); // true
// 6(掌握).boolean equalsIgnoreCase(String anotherString)
// 判断两个字符串是否相等,并且同时忽略大小写。
System.out.println("ABc".equalsIgnoreCase("abC")); // true
// 7(掌握).byte[] getBytes()
// 将字符串对象转换成字节数组
byte[] bytes = "abcdef".getBytes();
for(int i = 0; i < bytes.length; i++){
System.out.println(bytes[i]);
}
// 8(掌握).int indexOf(String str)
// 判断某个子字符串在当前字符串中第一次出现处的索引(下标)。
System.out.println("oraclejavac++.netc#phppythonjavaoraclec++".indexOf("java")); // 6
// 9(掌握).boolean isEmpty()
// 判断某个字符串是否为“空字符串”。底层源代码调用的应该是字符串的length()方法。
//String s = "";
String s = "a";
System.out.println(s.isEmpty());
// 10(掌握). int length()
// 面试题:判断数组长度和判断字符串长度不一样
// 判断数组长度是length属性,判断字符串长度是length()方法。
System.out.println("abc".length()); // 3
System.out.println("".length()); // 0
// 11(掌握).int lastIndexOf(String str)
// 判断某个子字符串在当前字符串中最后一次出现的索引(下标)
System.out.println("oraclejavac++javac#phpjavapython".lastIndexOf("java")); //22
// 12(掌握). String replace(CharSequence target, CharSequence replacement)
// 替换。
// String的父接口就是:CharSequence
String newString = "http://www.baidu.com".replace("http://", "https://");
System.out.println(newString); //https://www.baidu.com
// 把以下字符串中的“=”替换成“:”
String newString2 = "name=zhangsan&password=123&age=20".replace("=", ":");
System.out.println(newString2); //name:zhangsan&password:123&age:20
// 13(掌握).String[] split(String regex)
// 拆分字符串
String[] ymd = "1980-10-11".split("-"); //"1980-10-11"以"-"分隔符进行拆分。
for(int i = 0; i < ymd.length; i++){
System.out.println(ymd[i]);
}
String param = "name=zhangsan&password=123&age=20";
String[] params = param.split("&");
for(int i = 0; i <params.length; i++){
System.out.println(params[i]);
// 可以继续向下拆分,可以通过“=”拆分。
}
// 14(掌握)、boolean startsWith(String prefix)
// 判断某个字符串是否以某个子字符串开始。
System.out.println("http://www.baidu.com".startsWith("http")); // true
System.out.println("http://www.baidu.com".startsWith("https")); // false
// 15(掌握)、 String substring(int beginIndex) 参数是起始下标。
// 截取字符串
System.out.println("http://www.baidu.com".substring(7)); //www.baidu.com
// 16(掌握)、String substring(int beginIndex, int endIndex)
// beginIndex起始位置(包括)
// endIndex结束位置(不包括)
System.out.println("http://www.baidu.com".substring(7, 10)); //www
// 17(掌握)、char[] toCharArray()
// 将字符串转换成char数组
char[] chars = "我是中国人".toCharArray();
for(int i = 0; i < chars.length; i++){
System.out.println(chars[i]);
}
// 18(掌握)、String toLowerCase()
// 转换为小写。
System.out.println("ABCDefKXyz".toLowerCase());
// 19(掌握)、String toUpperCase();
System.out.println("ABCDefKXyz".toUpperCase());
// 20(掌握). String trim();
// 去除字符串前后空白
System.out.println(" hello world ".trim());
// 21(掌握). String中只有一个方法是静态的,不需要new对象
// 这个方法叫做valueOf
// 作用:将“非字符串”转换成“字符串”
//String s1 = String.valueOf(true);
//String s1 = String.valueOf(100);
//String s1 = String.valueOf(3.14);
// 这个静态的valueOf()方法,参数是一个对象的时候,会自动调用该对象的toString()方法吗?
String s1 = String.valueOf(new Customer());
//System.out.println(s1); // 没有重写toString()方法之前是对象内存地址 com.bjpowernode.javase.string.Customer@10f87f48
System.out.println(s1); //我是一个VIP客户!!!!
// 我们是不是可以研究一下println()方法的源代码了?
System.out.println(100);
System.out.println(3.14);
System.out.println(true);
Object obj = new Object();
// 通过源代码可以看出:为什么输出一个引用的时候,会调用toString()方法!!!!
// 本质上System.out.println()这个方法在输出任何数据的时候都是先转换成字符串,再输出。
System.out.println(obj);
System.out.println(new Customer());
}
}
class Customer {
// 重写toString()方法
@Override
public String toString() {
return "我是一个VIP客户!!!!";
}
}
4. StringBuffer
4.1 如果需要进行字符串的频繁拼接:
因为java中的字符串是不可变的,每一次拼接都会产生新字符串。这样会占用大量的方法区内存。造成内存空间的浪费,而StringBuffer这个类便可以解决这个问题
public class StringBufferTest02 {
public static void main(String[] args) {
// 创建一个初始化容量为16个byte[] 数组。(字符串缓冲区对象)
StringBuffer stringBuffer = new StringBuffer();
// 拼接字符串,以后拼接字符串统一调用 append()方法。
// append是追加的意思。
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("d");
stringBuffer.append(3.14);
stringBuffer.append(true);
// append方法底层在进行追加的时候,如果byte数组满了,会自动扩容。
stringBuffer.append(100L);
System.out.println(stringBuffer.toString());
// 指定初始化容量的StringBuffer对象(字符串缓冲区对象)
StringBuffer sb = new StringBuffer(100);
sb.append("hello");
sb.append("world");
sb.append("hello");
sb.append("kitty");
System.out.println(sb);
}
}
4.2 如何优化StringBuffer的性能
在创建StringBuffer的时候尽可能给定一个初始化容量,最好减少底层数组的扩容次数。预估计一下,给一个大一些初始化容量。
关键点:给一个合适的初始化容量。可以提高程序的执行效率。
4.3 String为什么是不可变的
通过源代码发现,String类中有一个byte[]数组,这个byte[]数组采用了final修饰,因为数组一旦创建长度不可变。并且被final修饰的引用一旦指向某个对象之后,不可再指向其它对象,所以String是不可变的!
4.4 StringBuilder/StringBuffer为什么是可变的
通过源代码发现,StringBuffer/StringBuilder内部实际上是一个byte[]数组,这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量我记得应该是16,当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy()…是这样扩容的。所以StringBuilder/StringBuffer适合于使用字符串的频繁拼接操作。
4.5 StringBuffer和StringBuilder的区别
- StringBuffer中的方法都有:synchronized关键字修饰。表示StringBuffer在多线程环境下运行是安全的。
- StringBuilder中的方法都没有:synchronized关键字修饰,表示StringBuilder在多线程环境下运行是不安全的。
- StringBuffer是线程安全的。
- StringBuilder是非线程安全的。
第十章 包装类
1. 包装类
1.1 包装类存在的意义
调用某个方法的时候需要传一个数字进去,但数字属于基本数据类型,而该方法参数的类型是Object,这也就引出了包装类
1.2 基本数据类型的包装类
基本数据类型 | 包装类型 |
---|---|
byte | java.lang.Byte(父类Number) |
short | java.lang.Short(父类Number) |
int | java.lang.Integer(父类Number) |
long | java.lang.Long(父类Number) |
float | java.lang.Float(父类Number) |
double | java.lang.Double(父类Number) |
boolean | java.lang.Boolean(父类Object) |
char | java.lang.Character(父类Object) |
- 以上八种包装类中,重点以java.lang.Integer为代表进行学习,八种包装类中其中6个都是数字对应的包装类,他们的父类都是Number,可以先研究一下Number中公共的方法:
- Number是一个抽象类,无法实例化对象。
- Number类中有这样的方法:
byte byteValue() 以 byte 形式返回指定的数值。<br /> abstract double doubleValue()以 double 形式返回指定的数值。<br /> abstract float floatValue()以 float 形式返回指定的数值。<br /> abstract int intValue()以 int 形式返回指定的数值。<br /> abstract long longValue()以 long 形式返回指定的数值。<br /> short shortValue()以 short 形式返回指定的数值。<br /> 这些方法其实所有的数字包装类的子类都有,这些方法是负责拆箱的。
1.3 Integer类的构造方法
Integer(int)
Integer(String)
通过访问包装类的常量,来获取最大值和最小值 System.out.println(“int的最大值:” + Integer.MAX_VALUE); System.out.println(“int的最小值:” + Integer.MIN_VALUE); System.out.println(“byte的最大值:” + Byte.MAX_VALUE); System.out.println(“byte的最小值:” + Byte.MIN_VALUE);
1.4 自动装箱和自动拆箱
- 在java5之后,引入了一种新特性,自动装箱和自动拆箱
- 自动装箱:基本数据类型自动转换成包装类
- 自动拆箱:包装类自动转换成基本数据类型
public class IntegerTest05 {
public static void main(String[] args) {
// 900是基本数据类型
// x是包装类型
// 基本数据类型 --(自动转换)--> 包装类型:自动装箱
Integer x = 900;
System.out.println(x);
// x是包装类型
// y是基本数据类型
// 包装类型 --(自动转换)--> 基本数据类型:自动拆箱
int y = x;
System.out.println(y);
// z是一个引用,z是一个变量,z还是保存了一个对象的内存地址。
Integer z = 1000; // 等同于:Integer z = new Integer(1000);
// 分析为什么这个没有报错呢?
// +两边要求是基本数据类型的数字,z是包装类,不属于基本数据类型,这里会进行自动拆箱。将z转换成基本数据类型
// 在java5之前你这样写肯定编译器报错。
System.out.println(z + 1);
Integer a = 1000; // Integer a = new Integer(1000); a是个引用,保存内存地址指向对象。
Integer b = 1000; // Integer b = new Integer(1000); b是个引用,保存内存地址指向对象。
// == 比较的是对象的内存地址,a和b两个引用中保存的对象内存地址不同。
// == 这个运算符不会触发自动拆箱机制。(只有+ - * /等运算的时候才会。)
System.out.println(a == b); //false
}
}
1.5 Integer非常重要的面试题
public class IntegerTest06 {
public static void main(String[] args) {
Integer a = 128;
Integer b = 128;
System.out.println(a == b); //false
/*
java中为了提高程序的执行效率,将[-128到127]之间所有的包装对象提前创建好,
放到了一个方法区的“整数型常量池”当中了,目的是只要用这个区间的数据不需要
再new了,直接从整数型常量池当中取出来。
原理:x变量中保存的对象的内存地址和y变量中保存的对象的内存地址是一样的。
*/
Integer x = 127;
Integer y = 127;
// == 永远判断的都是两个对象的内存地址是否相同。
System.out.println(x == y); //true
}
}
1.6 NumberFormatException
Integer a = new Integer("123");
// 编译的时候没问题,一切符合java语法,运行时会不会出问题呢?
// 不是一个“数字”可以包装成Integer吗?不能。运行时出现异常。
// java.lang.NumberFormatException
//Integer a = new Integer("中文");
1.7 Integer类当中有哪些常用的方法
public class IntegerTest07 {
public static void main(String[] args) {
// 重点方法
// static int parseInt(String s)
// 静态方法,传参String,返回int
//网页上文本框中输入的100实际上是"100"字符串。
后台数据库中要求存储100数字,此时java程序需要将"100"转换成100数字。
int retValue = Integer.parseInt("123"); // String -转换-> int
//int retValue = Integer.parseInt("中文"); // NumberFormatException
System.out.println(retValue + 100);
// 照葫芦画瓢
double retValue2 = Double.parseDouble("3.14");
System.out.println(retValue2 + 1); //4.140000000000001(精度问题)
float retValue3 = Float.parseFloat("1.0");
System.out.println(retValue3 + 1); //2.0
// -----------------------------------以下内容作为了解,不需要掌握---------------------------------------
// static String toBinaryString(int i)
// 静态的:将十进制转换成二进制字符串。
String binaryString = Integer.toBinaryString(3);
System.out.println(binaryString); //"11" 二进制字符串
// static String toHexString(int i)
// 静态的:将十进制转换成十六进制字符串。
String hexString = Integer.toHexString(16);
System.out.println(hexString); // "10"
// 十六进制:1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 16 17 18 19 1a
hexString = Integer.toHexString(17);
System.out.println(hexString); // "11"
//static String toOctalString(int i)
// 静态的:将十进制转换成八进制字符串。
String octalString = Integer.toOctalString(8);
System.out.println(octalString); // "10"
System.out.println(new Object()); //java.lang.Object@6e8cf4c6
// valueOf方法作为了解
//static Integer valueOf(int i)
// 静态的:int-->Integer
Integer i1 = Integer.valueOf(100);
System.out.println(i1);
// static Integer valueOf(String s)
// 静态的:String-->Integer
Integer i2 = Integer.valueOf("100");
System.out.println(i2);
}
}