第1章-第5节
一、知识点
内部类、Object、字符串。
二、目标
- 掌握内部类语法。
- 理解Object类以及Object常用方法。
- 深刻理解字符串常量池。
- 掌握String、StringBuffer、StringBuilder的区别。
三、内容分析
- 重点
- Object类、String类、String常用方法的使用、字符串常量池。
- 难点
- 字符串常量池。
- 灵活应用String常用方法解决实际问题。
四、内容
1、内部类
1.1 定义
把类定义在另一个类的内部,该类就被称为内部类。
作用:更好的隐藏细节,类似于辅助类,一般用于一个类没有多处用到的时候,例如LinkList。
1.2 语法
class 类名 {
// 内部类
class 类名 {
}
}
1.3 访问方式
外部类.内部类 变量名 = new 外部类对象.new 内部类对象
-
本类可以直接创建内部类对象。(要放入方法中,让对象调用这个方法来创建内部类对象)
-
内部类可以访问外部类的属性和方法。(创建内部类的时候是通过外部类的,所以调用内部类内容的时候我们已经有一个外部类了)
//外部类想使用内部类的内容必须通过在外部类中创建一个内部类的对象来进行调用
//外部类与内部类可以有同名属性与方法,但是调用的时候默认是调用内部类的,可以通过外部类名.this.属性/方法来调用外部类的内容而内部类就是this.属性/方法;
1.4 匿名内部类
继承或者实现接口的时候,如果子类只用到了一次,可以直接使用匿名内部类实现,就不用单独再创建一个类来实现。
接口 变量 = new 接口() {
};
2、Object(超类)
Object是所有类的根类,所有的类都是直接或者间接继承Object类。
java一定是单继承的,故已经继承了父类的类,它是间接继承Object,只要继承的这一条线上有一个继承了Object,那么其子类都继承了Object
1.toString()
直接输出对象,输出的结果是它的地址字符串,其实这与打印对象.toString一样
返回该对象的字符串表示。通常toString方法会返回一个“文本方式表示”此对象的字符串。结果应该是一个简明易懂的信息表达式。建议所有的子类(实体类:要去使用的类)都重写此方法(可以用于快速的知道这个对象里面有什么内容)。(右键选择Generate,选择toString就可以快速重写toString方法)
getClass().getName() + "@" + Integer.toHexString(hashCode());
com.mashang.PengYuYan@6e0be858
hex表示16进制,Integer是int的一个包装类型
2. hashCode()
概念:对象在创建的时候,jvm会给每一个对象分配一个独一无二的内存地址,将这个地址转化为整数形式返回。
作用:加快查找速率,在哈希表中的寻址操作
由Object类定义的hashCode方法给不同的对象返回不同的整数
相同的对象,hashCode是一样的(地址—算法得到数值),反过来不一定。
//同一个程序,相同地址一定是相同对象,不同程序,相同地址不一定同一个对象
//同一个程序中->判断两个对象是否相同,只需要看它的hashCode得到的地址是否相同即可
//当if(person1 == person2)时,括号内判断的不是这两个对象toString的结果,而是判断这两个对象的地址是否相等,但是一般使用equals()来比较地址
3. finalize
用于垃圾回收,我们不用手动去调用,由JVM来调用,当垃圾回收器确定不存在该对象的引用时,由对象的垃圾回收器调用此方法。
4. equals():内核还是比较地址:即就是双等于号
即源码里面equals()就是"=="
重写toString()的地方(即实体类)也建议重写equals()
引申1:普通数据类型比较值、引用数据类型比较地址
引申2:自定义类重写equals用来比较这两个对象是否相等,重写equals方法需要重写hashCode方法与equal方法
重写hashCode的理由:对象内存地址由虚拟机来管理的,所以这个值是由操作系统以及硬件来共同计算出来的,是不可预知的。因此,即使两个对象在代码中看起来完全相同,它们的内存地址也可能是不同的,因此它们的hashCode()方法生成的哈希值也会不同。所以我们需要重写 hashCode 方法。
右键选择Generate,然后选择equals()与hashCode()快速重写
JNI:Java调用计算机底层硬件的途径。Java需要通过C语言来调用,通过JNI可以让Java调用c/c++代码,通过他们来调用系统硬件,像Object里的notify方法就是JNI方法之一。
3、字符串
3.1 String
与其他引用数据类型不同,其传入函数中的虽然是引用,但是函数内对其的操作不会影响其本身
字符串就是一系列的字符组合的串,在Java中单引号’'表示字符,双引号""表示字符串。
字符串使用String类表示,值得注意的是它并不是基本数据类型,是引用类型。
3.2 声明字符串
- 直接创建
String str = “今天又是被帅醒的一天”;
- 使用new创建
String str1 = new String(“Hello”);
- 直接创建与new创建的区别
字符串池:当我们创建一个字符串时,JVM 会先对这个字符串进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回;否则,创建新的字符串对象,然后将这个引用放入字符串常量池,并返回该引用。
String str1 = “Hello”;
String str2 = “Hello”;
String str3 = new String(“Hello”);
String str4 = new String(“Hello”);
字符串内存示意图:
故若是想比较字符串是否一样,最好用.equals()
原理:
- 首先比较是否为同一对象(相同则返回ture)
- 比较()中传入的对象是否为String类型(否->返回false)
- 对象强转为字符串
- 比较字符串的长度是否一样(否->返回false)
- 将字符串转换为字符数组,然后每一个字符一一比较
思考1:
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str3 == str4);
思考2:
String str1 = “hello”;//常量池
String str2 = “hello”;//常量池
str = str1+“world”;//堆
//字符串常量池是怎么运作的?原理是什么?
System.out.println(str1);
System.out.println(str2);
3.3 拼接字符串
1、常量与常量的拼接是在常量池,编译期优化;
2、若拼接过程中存在变量,则结果在堆里(相当于使用new),不在常量池;拼接的原理是用StringBuilder;
3、若拼接结果使用intern()方法,则将常量池中还没有的对象放入常量池,并返回对象;
思考3:
String s1 = "hello";
String s2 = "hell"+"o";//jvm会进行编译期优化
System.out.println(s1==s2);
思考4:(面试重点题)
String str = new String(“abc”) ,其中,创建了几个对象?
答案:要看常量池中是否存在“abc”字符串,如果有,则1个,如果没有,则两个(一个在堆中的String变量,一个在常量池中的字符串常量“abc”)
思考5:(面试重点题)intern()函数的作用:保证字符串在内存地址中的唯一性
将常量池中还没有的对象放入常量池,并返回对象
student.name = new String("彦祖");
String n = "彦祖";
System.out.println(student.name==n); //false
student.name = new String("彦祖").intern();
String n = "彦祖";
System.out.println(student.name==n); //true
3.4 操作字符串
以下方法是字符串常用的方法,更多方法查看文档。
方法 | 描述 |
---|---|
public boolean equals(Object anObject) | 比较字符串的内容,严格区分大小写。 |
public boolean equalsIgnoreCase(String anotherString) | 比较字符串的内容,忽略大小写。 |
public int length() | 返回此字符串的长度。 |
public char charAt(int index) | 返回指定索引处的 char 值。 |
public char[] toCharArray() | 将字符串拆分为字符数组后返回。 |
public String substring(int beginIndex, int endIndex) | 根据开始和结束索引进行截取,得到新的字符串(包含头,不包含尾)。 |
public String substring(int beginIndex) | 从传入的索引处截取,截取到末尾,得到新的字符串。 |
public String replace(CharSequence target, CharSequence replacement) | 使用新值,将字符串中的旧值替换,得到新的字符串。 |
public String[] split(String regex) | 根据传入的规则切割字符串,得到字符串数组。 |
public byte[] getBytes() | 获得当前字符串底层的字节数组。 |
以上的方法都不会对原字符串造成影响,只是会产生一个新字符串
注意:
String字符串可以使用“==”和equals()方法比较。当两个字符串使用“==”进行比较时,比较的是两个字符串在内存中的地址。当两个字符串使用equals方法比较时,比较的是两个字符串的值是否相等,String已经对equals进行了重写。思考以下打印结果。
String str1 = “abc”;
String str2 = “abc”;
String str3 = new String(“abc”);
String str4 = new String(“abc”);
String str5 = str1;
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str3 == str4);
System.out.println(str1 == str5);
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
System.out.println(str3.equals(str4));
System.out.println(str1.equals(str5));
3.4 StringBuffer
线程安全的可变字符串。
可变:直接把变量引用的字符串改变
线程:多个线程同时操作一个资源的时候,可能会发生数据安全问题。
StringBuffer是线程安全的,创建后值可以变化,可以添加、修改、删除值,但不会改变地址,不会产生新的字符串。
线程安全有利有弊,它提高了安全性,但是牺牲了性能。
StringBuffer str1 = new StringBuffer("abc");
String str2 = "abc";//str1与str2指向的abc地址相同
str1.append("123");
sout(str1);//输出abc123
sout(str2);//输出abc
尽管str1与str2最初指向的是同一个"abc"
但是append之后abc->abc123
而因为str2引用的abc,故系统会保留一个abc给str2
故str2是指向了一个不同地址的abc了
3.5 StringBuilder
1、非线程安全的可变字符串。
2、用法和StringBuffer一样,只是StringBuilder是非线程安全的。
3、使用了toString()方法创建String对象
思考6: 下述代码中产生了几个对象?
String string = new String(“彦祖”)+new String(“牛逼”)
3.5 String
是不可变字符串
即变量的引用是可以改变的,但是字符串常量池中的字符串不可以被改变
五、小结
本小节介绍了内部类,Object基类,以及字符串。重点理解匿名内部类的写法,熟练应用字符串的内置方法解决问题。
七、预习
异常、随机数、包装类、日期类。
思考题:
-
异常的作用。
-
什么是包装类。
-
如何获取当前时间年月日。