前言:菜鸟一枚,如有问题欢迎大家在评论区指出,我们一起学习。
1.9.1:代码块以及类中代码执行顺序
代码块:用大括号括起来的代码叫做代码块。代码块分为普通代码块,静态代码块和构造代码块。
普通代码块:方法里
public void methodOne(){
{
System.out.println("普通代码块");
}
}
静态代码块:类中,方法外,用static修饰
static{
System.out.println("静态代码块");
}
构造代码块:类中,方法外
{
System.out.println("构造代码块");
}
静态属性随着类的加载出现,能直接用 类名. 来访问,而构造器要创建对象时才能使用。当我们用构造器来给静态属性赋值时,我们不能在对象创建前访问它的值,所以我们可以用静态代码块来给静态属性赋值。如何赋值,我们需要先了解类中代码的执行顺序。
示例代码:单个类中
public class CodeBlock {
static{
System.out.println("静态代码块");
}
{
System.out.println("构造代码块");
}
public CodeBlock(){
System.out.println("无参构造函数");
}
public void sayHello(){
{
System.out.println("普通代码块");
}
}
public static void main(String[] args) {
System.out.println("执行了main方法");
CodeBlock cb1 = new CodeBlock();
cb1.sayHello();
System.out.println("---------------");
CodeBlock cb2 = new CodeBlock();
cb2.sayHello();
}
}
代码执行顺序:在单个类中,随着类的加载先执行静态内容(静态属性和静态代码块根据定义顺序决定先后),然后当创建对象时(new()),执行构造代码块,最后构造函数才开始执行。 注意:静态代码块不能存在于任何方法内,即使静态方法可以使用静态变量;创建对象时,前面若有代码也会先执行;静态代码块只会执行一次 上面代码均为自动进行,对于普通方法或者静态方法中的代码块,需要主动通过对象名.方法名或者类名.方法名来执行
示例代码:父子类中
public class Father {
static{
System.out.println("父类静态代码块");
}
{
System.out.println("父类构造代码块");
}
public Father(){
System.out.println("父类构造函数");
}
public void methodOne(){
System.out.println("父类普通代码块");
}
}
public class Son extends Father{
static{
System.out.println("子类静态代码块");
}
{
System.out.println("子类构造代码块");
}
public Son(){
System.out.println("子类构造函数");
}
}
public class TestSon {
public static void main(String[] args) {
System.out.println("main方法");
Son son1 = new Son();
son1.methodOne();
System.out.println("------------------");
Son son2 = new Son();
}
}
执行顺序:在有父子类时,先执行创建对象前的代码(此时子类还未加载),然后加载子类。 首先执行父类静态的内容,父类静态的内容执行完毕后,接着去执行子类的静态的内容。 当子类的静态内容执行完毕之后,再去看父类有没有构造代码块,如果有就执行父类的构造代码块。 父类的构造代码块执行完毕,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有构造代码块,如果有就执行子类的构造代码块。子类的构造代码块执行完毕再去执行子类的构造方法。 注意:和单个类一样,普通代码块都是需要主动执行的;在父子类中,构造代码块和构造函数是一体的。
1.9.2:Object根父类
Object类有5个重要的方法,分别为toString(),equals(),getClass(),hashCode(),finalize()。
toString():
①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
③重写使其打印对象时直接打印对象的属性值
重写toString()代码: stuId,stuName,stuAge,stuGender是属性
@Override
public String toString() {
return "姓名:" + stuName + ",年龄:" + stuAge + ",性别:" + stuGender;
}
equals():
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写,使其比较两个对象的属性值是否一致,即是否为业务逻辑上的同一个对象
重写equals()代码:stuId,stuName,stuAge,stuGender是属性
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return getStuID() == student.getStuID() && getStuAge() == student.getStuAge()
&& Objects.equals(getStuName(), student.getStuName())
&& Objects.equals(getStuGender(), student.getStuGender());
}
注意: 比较两个字符串是否相等也不能用==,也需要用到s1.equals(s2),但是这个equals()方法并不是Object类里面的equals()方法。
getClass():
因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
注意: 虽然obj.getclass()可以返回运行时的类与特定类进行比较,但是它只能判断对象是否属于特定的类,而不能判断其是否属于父类或接口。使用 obj instanceof obj2 方法,可以用来判断一个对象是否是某个类或其子类的实例。它不仅可以判断对象是否属于某个具体类,还可以判断其是否属于某个接口。还有一种反射机制用于判断,可参考下面链接。
java如何判断一个对象是否属于某个类 | PingCode智库https://docs.pingcode.com/baike/175087
hashCode():
1.如果两个对象的hash值是不同的,那么这两个对象一定不相等;
2.如果两个对象的hash值是相同的,那么这两个对象不一定相等。
重写hashCode()代码:stuId,stuName,stuAge,stuGender是属性
@Override
public int hashCode() {
return Objects.hash(getStuID(), getStuName(), getStuAge(), getStuGender());
}
finalize():
面试题:对finalize()的理解?
当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码
每一个对象的finalize方法只会被调用一次。
子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存
1.9.3:final关键字
final修饰类:表示这个类不能被继承,没有子类
final class Eunuch{//太监类
}
class Son extends Eunuch{//错误
}
final修饰方法: 表示这个方法不能被子类重写
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
注意:对于static修饰的方法,不能被子类重写,因此static不能和final同时存在,冗余。
final修饰变量: final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。
final修饰成员变量时,必须赋值,因为成员变量会自动赋初值,如果不自己赋初值,final不允许再次修改值。
final修饰局部变量时,可以先定义,然后再赋初值。
1.9.4:多态
多态:任何一个需要父类引用的位置都可以传入一个子类的对象。编译时类型与运行时类型不一致,编译时看“父类”,运行时看“子类”。
多态实现的三个步骤: 1.必须有继承,必须有子类 2.父类定义方法,子类重写方法 3.父类的引用指向子类的对象弊端:编译时,只能调用父类声明的方法,不能调用子类扩展的方法;
好处:运行时,看“子类”,如果子类重写了方法,一定是执行子类重写的方法体;变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。
多态示例代码:
public class Pet {
private String name;
private int age;
public Pet() {
}
public Pet(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void play(){
System.out.println("我们玩吧");
}
}
public class Dog extends Pet{
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
@Override
public void play() {
super.play();
System.out.println("我们玩飞盘吧");
}
public void work(){
System.out.println("正在工作");
}
}
public class TestTwo {
public static void main(String[] args) {
//多态中只能看见子类重写的方法,看不见子类独有的方法,但能通过转型来使用
Pet pet = new Dog();//向上转型(自动),本体是儿子,身份伪装成爸爸,能力暂时被限制
pet.play();
// pet.work(); 不能调用子类的方法
Dog dog = (Dog)pet;//向下转型(手动),撕破伪装,能力重启
pet.play();
}
}
对象的类型转换:
(1)子类转父类
Son s = new Son();
// 类型提升
Father f = (Father)s;//可以
分析: 子类继承父类,就拥有了父类的一切。某种程度上说,儿子可以代替爸爸,爸爸能做的事儿子也可以做。子类转父类后,父类对象f引用指向子类对象,所以其本质任然是子类,f只能调用父类的方法,如果子类重写了父类的方法,则调用的是子类的方法(多态性)
(2)父类转子类
1. 真实父类对象转子类对象,报ClassCastException异常
Father f = new Father();
Son s = (Son)f;//出错 ClassCastException
分析: 创建一个父类的实例,想要强制把父类对象转换成子类的对象,是不行的。父亲有的,通过继承儿子也有,反过来儿子有的父亲却不一定有。
2. “假”父类对象转子类对象
Father f = new Son();
Son s = (Son)f;//可以
分析:只有父类对象本身就是用子类new出来的时候, 才可以在将来被强制转换为子类对象。这个时候父类的本质依然是子类对象(儿子只是装成了爸爸),子类有的属性f都拥有,只是f暂时不能操作子类特有的属性,所以可以转换回为子类对象(变回儿子本身)