目录
(6)重写toString、equals和hashCode方法(Alt+Insert)
1、非静态代码块的作用
,与构造器相同,都是为了为初始化做的准备,用于实例变量初始化等操作;
2、非静态代码块的意义
如果多个重载的构造器有公共代码,并且这些代码都是先于构造器其他代码执行的,那么可以将这部分代码抽取到非静态代码块中,减少冗余代码。
大白话:提取公共的代码到一个区域里,都是初始化对吧,就是为了减少代码。
3、非静态代码块的执行特点
所有非静态代码块中代码都是在new对象时自动执行,并且一定是先于构造器的代码执行
精简: new对象的时候自动执行,先于构造器执行。
4、非静态代码块的语法格式
【修饰符】 class 类{
{
非静态代码块
}
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
}
{
非静态代码块
}
5、非静态代码块的应用
public class User {
private String username;
private String password;
private String registrationTime;
// 非静态代码块
{
System.out.println("新用户注册");
}
//构造器
// 无参构造
public User() {
}
//有参构造
public User(String username, String password) {
this.username = username;
this.password = password;
long s= System.currentTimeMillis();
// long s1 =new Date().getTime();
// long s2 = Calendar.getInstance().getTimeInMillis();
// long s3= Instant.now().toEpochMilli();
// long s4 = Clock.systemUTC().millis();
// String str ="";
// 设置日期格式
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = f.format(new Date(s));
this.registrationTime = str;
}
// 信息显示
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", registrationTime=" + registrationTime +
'}';
}
// get、set
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRegistrationTime() {
return registrationTime;
}
public void setRegistrationTime(String registrationTime) {
this.registrationTime = registrationTime;
}
// 青蛙跳台阶,可以跳1个台阶,也可以跳两个台阶,求n个台阶有多少方式
// n个台阶的方法=第n-1个台阶的方法是+第n-2个台阶的方式;
// 一个台阶是1种,二个台阶2种,三个台阶3种,四个台阶5种,五个台阶8种;
// 方法递归
public static int jump(int n){
if(n==1){
return 1;//就会最后一个结束递归的1;
}else if(n==2){
return 2;//结束递归的条件2
}else {
return jump(n-1)+jump(n-2);
}
}
// 非递归
public static int jump2(int n){
if(n==1){
return 1;//当台阶为1是,一种
}else if(n==2){
return 2;//台阶为2是 ,2种
}else{
int f1= 1;
int f2= 2;
int cur=0;
for (int i = 3; i <=n ; i++) {//i代表台阶数
cur = f1+f2;//
f1=f2;
f2=cur;
}
return cur;
}
}
public static void main(String[] args) {
User user = new User();
System.out.println(user);
User user1 = new User("三","123456");
System.out.println(user1.toString());
int num = 5;
System.out.println(User.jump(num));
// 非递归调用
System.out.println(jump2(6));
}
}
实例初始化过程(了解)
1、实例初始化的目的
实例初始化的过程其实就是在new对象的过程中为实例变量赋有效初始值的过程 。
2、实例初始化相关代码
在new对象的过程中给实例变量赋初始值可以通过以下3个部分的代码完成:
(1)实例变量直接初始化
(2)非静态代码块
(3)构造器
当然,如果没有编写上面3个部分的任何代码,那么实例变量也有默认值。
3、实例初始化方法
实际上我们编写的代码在编译时,会自动处理代码,整理出一个或多个的<init>(...)实例初始化方法。一个类有几个实例初始化方法,由这个类就有几个构造器决定。
实例初始化方法的方法体,由4部分构成:
(1)super()或super(实参列表)
-
这里选择哪个,看原来构造器首行是super()还是super(实参列表)
-
如果原来构造器首行是this()或this(实参列表),那么就取对应构造器首行的super()或super(实参列表)
-
如果原来构造器首行既没写this()或this(实参列表),也没写super()或super(实参列表) ,默认就是super()
(2)非静态实例变量的显示赋值语句
(3)非静态代码块
(4)对应构造器中剩下的的代码
特别说明:其中(2)和(3)是按顺序合并的,(1)一定在最前面(4)一定在最后面
4、实例初始化执行特点
-
创建对象时,才会执行
-
每new一个对象,都会完成该对象的实例初始化
-
调用哪个构造器,就是执行它对应的<init>实例初始化方法
-
子类super()还是super(实参列表)实例初始化方法中的super()或super(实参列表) 不仅仅代表父类的构造器代码了,而是代表父类构造器对应的实例初始化方法。
public class Father {
private int a = 1;
public Father(){
System.out.println("Father类的无参构造");
}
public Father(int a, int b){
System.out.println("Father类的有参构造");
this.a = a;
this.b = b;
}
{
System.out.println("Father类的非静态代码块1,a = " + a);
System.out.println("Father类的非静态代码块1,b = " + this.b);
}
private int b = 1;
{
System.out.println("Father类的非静态代码块2,a = " + a);
System.out.println("Father类的非静态代码块2,b = " + b);
}
public String getInfo(){
return "a = " + a + ",b = " + b;
}
}
public class TestFather {
public static void main(String[] args) {
Father f1 = new Father();
System.out.println(f1.getInfo());
System.out.println("-----------------------");
Father f2 = new Father(10,10);
System.out.println(f2.getInfo());
}
}
关键字和API
this和super关键字
1.this和super的意义
this:当前对象
-
在构造器和非静态代码块中,表示正在new的对象
-
在实例方法中,表示调用当前方法的对象
super:引用父类声明的成员
无论是this和super都是和对象有关的。
2.this和super的使用格式
-
this
-
this.成员变量:表示当前对象的某个成员变量,而不是局部变量
-
this.成员方法:表示当前对象的某个成员方法,完全可以省略this.
-
this()或this(实参列表):调用另一个构造器协助当前对象的实例化,只能在构造器首行,只会找本类的构造器,找不到就报错
-
//构造器
public 构造器1(String name ,int age){
this.name=name;
this.age=age;
}public 构造器2 (String name , int age , String address,String gender){
this(name,age);//调用了构造器1协助完成当前构造器的初始化;
this.address = address;
this.gender= gender;
}
-
super
-
super.成员变量:表示当前对象的某个成员变量,该成员变量在父类中声明的
-
super.成员方法:表示当前对象的某个成员方法,该成员方法在父类中声明的
-
super()或super(实参列表):调用父类的构造器协助当前对象的实例化,只能在构造器首行,只会找直接父类的对应构造器,找不到就报错
-
注意:super()默认是隐藏的看不见,但它就是存在这个父类的无参构造
3.避免子类和父类声明重名的成员变量
特别说明:应该避免子类声明和父类重名的成员变量
因为,子类会继承父类所有的成员变量,所以:
-
如果重名的成员变量表示相同的意义,就无需重复声明
-
如果重名的成员变量表示不同的意义,会引起歧义
在阿里的开发规范等文档中都做出明确说明
4.解决成员变量重名问题
-
如果实例变量与局部变量重名,可以在实例变量前面加this.进行区别
-
如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加super.,否则默认访问的是子类自己声明的实例变量
-
如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用this.实例访问,也可以用super.实例变量访问
总结:起点不同(就近原则)
-
变量前面没有super.和this.
-
在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的==局部变量==,
-
如果不是局部变量,先从当前执行代码的==本类去找成员变量==
-
如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量==(权限修饰符允许在子类中访问的)
-
-
变量前面有this.
-
通过this找成员变量时,先从当前执行代码的==本类去找成员变量==
-
如果从当前执行代码的本类中没有找到,会往上找==父类声明的成员变量(==权限修饰符允许在子类中访问的)
-
-
变量前面super.
-
通过super找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的)
-
如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)
-
5.解决成员方法重写后调用问题
-
如果子类没有重写父类的方法,只有权限修饰符运行,在子类中完全可以直接调用父类的方法;
-
如果子类重写了父类的方法,在子类中需要通过super.才能调用父类被重写的方法,否则默认调用的子类重写的方法
总结:
-
方法前面没有super.和this.
-
先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
-
-
方法前面有this.
-
先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
-
-
方法前面有super.
-
从当前子类的直接父类找,如果没有,继续往上追溯
-
6.5.2 native关键字
1.native的意义
native:本地的,原生的
2.native的语法
native只能修饰方法,表示这个方法的方法体代码不是用Java语言实现的,而是由C/C++语言编写的。但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它。
JVM内存的管理:
区域名称 | 作用 |
---|---|
程序计数器 | 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址 |
本地方法栈 | 当程序中调用了native的本地方法时,本地方法执行期间的内存区域 |
方法区 | 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 |
堆内存 | 存储对象(包括数组对象),new来创建的,都存储在堆内存。 |
虚拟机栈 | 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。 |
final关键字
1.final的意义
final:最终的,不可更改的
2.final修饰类
表示这个类不能被继承,没有子类
final class Eunuch{//太监类
}
class Son extends Eunuch{//错误
}
3.final修饰方法
表示这个方法不能被子类重写
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
final修饰变量
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。
如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
public class TestFinal {
public static void main(String[] args){
final int MIN_SCORE = 0;
final int MAX_SCORE = 100;
MyDate m1 = new MyDate();
System.out.println(m1.getInfo());
MyDate m2 = new MyDate(2022,2,14);
System.out.println(m2.getInfo());
}
}
class MyDate{
//没有set方法,必须有显示赋值的代码
private final int year;
private final int month;
private final int day;
public MyDate(){
year = 1970;
month = 1;
day = 1;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public int getMonth() {
return month;
}
public int getDay() {
return day;
}
public String getInfo(){
return year + "年" + month + "月" + day + "日";
}
}
final修饰对象
public class TestFinal {
private static Random rand = new Random();//实例化一个Random对象
//随机产生0——10的随机数赋予定义为final的a1
private final int a1 = rand.nextInt(10);
//随机产生0——10的随机数赋予定义为final的a2
private static final int a2 = rand.nextInt(10);
public static void main(String[] args){
TestFinal fdata = new TestFinal();//实例化一个对象
//调用定义为final的a1
System.out.println("实例化对象调用a1的值:"+fdata.a1);
//调用定义为static final的a2
System.out.println("实例化对象调用a2的值:"+fdata.a2);
//实例化另外一个对象
TestFinal fdata2 = new TestFinal();
System.out.println("重新实例化对象调用a1的值:"+fdata2.a1);
System.out.println("重新实例化对象调用a2的值:"+fdata2.a2);
}
// 看出final修饰的对象在该对象重新实例化后被重新赋值,
// 也就是说final修饰的对象只能指向唯一一个对象,
// 不可以再将它指向其他对象,
// 而static final修饰的对象则可以使一个常量真正做到不被修改
}
6.5.4 Object根父类
1.如何理解根父类
类 java.lang.Object
是类层次结构的根类,即所有类的父类。每个类都使用 Object
作为超类。
-
Object类型的变量与除Object以外的任意引用数据类型的对象都多态引用
-
所有对象(包括数组)都实现这个类的方法。
-
如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
public class MyClass /*extends Object*/ {
// ...
}
2.Object类的其中5个方法
API(Application Programming Interface),应用程序编程接口。Java API是一本程序员的字典
,是JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么我们需要查看src源码。
(1)toString()//可以查看当前的信息
方法签名:public String toString()
①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②通常是建议重写
③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()。
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
(2)getClass() (查看对象运行时类型)
public final Class<?> getClass():获取对象的运行时类型
因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
public static void main(String[] args) {
Object obj = new Person();
System.out.println(obj.getClass());//运行时类型
}
(3)equals()
public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写,重写有些要求:
A:
B:如果重写equals,那么一定要遵循如下几个原则:
a:自反性:x.equals(x)返回true
b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true
c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
d:对称性:x.equals(y)与y.equals(x)结果应该一样
e:非空对象与null的equals一定是false
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + ((password == null) ? 0 : password.hashCode());
result = prime * result + ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
}
(4)hashCode()
public int hashCode():返回每个对象的hash值。
如果重写equals,那么通常会一起重写hashCode()方法,hashCode()方法主要是为了当对象存储到哈希表(后面集合章节学习)等容器中时提高存储和查询性能用的,这是因为关于hashCode有两个常规协定:
-
①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
-
②如果两个对象的hash值是相同的,那么这两个对象不一定相等。
原因就是因为十进制的问题
public static void main(String[] args) {
System.out.println("Aa".hashCode());//2112
System.out.println("BB".hashCode());//2112
}
重写equals和hashCode方法时,要保证满足如下要求:
-
①如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
-
②如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
-
③如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false
(5)finalize()
protected void finalize():用于最终清理内存的方法
public class TestFinalize {
public static void main(String[] args) throws Throwable{
for (int i=1; i <=10; i++){
MyDemo my = new MyDemo(i);
//每一次循环my就会指向新的对象,那么上次的对象就没有变量引用它了,就成垃圾对象
}
//为了看到垃圾回收器工作,我要加下面的代码,让main方法不那么快结束,因为main结束就会导致JVM退出,GC也会跟着结束。
System.gc();//如果不调用这句代码,GC可能不工作,因为当前内存很充足,GC就觉得不着急回收垃圾对象。
//调用这句代码,会让GC尽快来工作。
Thread.sleep(5000);//单位是毫秒,让当前程序休眠5秒再结束
}
}
class MyDemo{
private int value;
public MyDemo(int value) {
this.value = value;
}
@Override
public String toString() {
return "MyDemo{" + "value=" + value + '}';
}
//重写finalize方法,让大家看一下它的调用效果
@Override
protected void finalize() throws Throwable {
// 正常重写,这里是编写清理系统内存的代码
// 这里写输出语句是为了看到finalize()方法被调用的效果
System.out.println(this+ "轻轻的走了,不带走一段代码....");
}
}
每一个对象的finalize()只会被调用一次,哪怕它多次被标记为垃圾对象。当一个对象没有有效的引用/变量指向它,那么这个对象就是垃圾对象。GC(垃圾回收器)通常会在第一次回收某个垃圾对象之前,先调用一下它的finalize()方法,然后再彻底回收它。但是如果在finalize()方法,这个垃圾对象“复活”了(即在finalize()方法中意外的又有某个引用指向了当前对象,这是要避免的),被“复活”的对象如果再次称为垃圾对象,GC就不再调用它的finalize方法了,避免这个对象称为“僵尸”。
public class TestFinalize {
private static MyDemo[] arr = new MyDemo[10];
private static int total;
public static void add(MyDemo demo){
arr[total++] = demo;
}
public static void main(String[] args) throws Throwable{
for (int i=1; i <=10; i++){
MyDemo my = new MyDemo(i);
//每一次循环my就会指向新的对象,那么上次的对象就没有变量引用它了,就成垃圾对象
}
//为了看到垃圾回收器工作,我要加下面的代码,让main方法不那么快结束,因为main结束就会导致JVM退出,GC也会跟着结束。
System.gc();//如果不调用这句代码,GC可能不工作,因为当前内存很充足,GC就觉得不着急回收垃圾对象。
//调用这句代码,会让GC尽快来工作。
Thread.sleep(5000);//单位是毫秒,让当前程序休眠5秒再结束
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);//MyDemo的对象还在,没有被回收掉,因为在回收过程中被复活了
}
for (int i = 0; i < arr.length; i++) {
arr[i] = null;//让这些元素不引用MyDemo的对象,这些对象再次称为垃圾对象
System.out.println(arr[i]);
}
arr = null;
System.gc();//再次让GC工作,使得MyDemo的对象再次被回收
Thread.sleep(5000);//单位是毫秒,让当前程序休眠5秒再结束
}
}
class MyDemo{
private int value;
public MyDemo(int value) {
this.value = value;
}
@Override
public String toString() {
return "MyDemo{" + "value=" + value + '}';
}
//重写finalize方法,让大家看一下它的调用效果
@Override
protected void finalize() throws Throwable {
// 正常重写,这里是编写清理系统内存的代码
// 这里写输出语句是为了看到finalize()方法被调用的效果
System.out.println("我轻轻的走了,不带走一段代码....");
TestFinalize.add(this);
//把当前对象this放到一个数组中,这样就有变量引用它,当前对象就不能被回收了
//当下次this对象再次称为垃圾对象之后,GC就不会调用它的finalize()方法了
}
}
-
当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
-
这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码
-
每一个对象的finalize方法只会被调用一次。
-
子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存
(6)重写toString、equals和hashCode方法(Alt+Insert)
建议使用IDEA中的Alt + Insert快捷键,而不是Ctrl + O快捷键。
3.标准JavaBean
JavaBean
是 Java语言编写类的一种标准规范。符合JavaBean
的类,要求:
(1)类必须是具体的和公共的,
(2)并且具有无参数的构造方法,
(3)成员变量私有化,并提供用来操作成员变量的set
和get
方法。
(4)重写toString方法
public class ClassName{
//成员变量
//构造方法
//无参构造方法【必须】
//有参构造方法【建议】
//getXxx()
//setXxx()
//其他成员方法
}
编写符合JavaBean
规范的类,以学生类为例,标准代码如下:
public class Student {
// 成员变量
private String name;
private int age;
// 构造方法
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// get/set成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
//其他成员方法列表
public String toString(){
return "姓名:" + name + ",年龄:" + age;
}
}
测试类,代码如下:
public class TestStudent {
public static void main(String[] args) {
// 无参构造使用
Student s = new Student();
s.setName("柳岩");
s.setAge(18);
System.out.println(s.getName() + "---" + s.getAge());
System.out.println(s);
// 带参构造使用
Student s2 = new Student("赵丽颖", 18);
System.out.println(s2.getName() + "---" + s2.getAge());
System.out.println(s2);
}
}
static.