java动态绑定机制⭐⭐⭐⭐⭐
① 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定;
② 当调用对象方法中的属性时,没有动态绑定机制,哪里声明,哪里使用。
③ 当调用类成员变量:全局变量属性时,该**(全局)属性会和编译类型绑定**
public class test{
public static void main(String[] args){
A a = new B();//a的编译类型是 A,运行类型是B
System.out.println(a.i);
System.out.println(a.sum());
System.out.println(a.sum1());
}
}
class A{ //父类
public int i = 10;
public int sum(){
return i + 10;
}
public int sum1(){
return i + 20;
}
}
class B extends A{ //子类
public int i = 20;
public int sum(){
return i + 10;
}
public int sum2(){
return i + 20;
}
}
此时结果为10(全局属性,与编译类型A绑定,调用的A的属性),30(与运行类型B绑定),30(与运行类型B绑定,B没有,向上查找A)。
super关键字
1、基本介绍
super代表父类的引用,用于访问父类的属性、方法、构造器。
2、基本语法
public class A {
//父类的四个属性
public int n1;
protected int n2;
int n3;
private int n4;
//父类的三个构造器
public A() {}
public A(String name) {}
public A(String name,int age) {}
//父类的四个方法
public void test100() {}
protected void test200() {}
void test300() {}
private void test400() {}
}
① 访问父类的属性,但不能访问父类的private属性;
例如:super.属性名;
public class B extends A{
public void hi(){
System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
}
}
② 访问父类的方法,不能访问父类的private方法;
例如:super.方法名(参数列表);
public void hi2(){
super.test100();
super.test200();
super.test300();
}
③ 访问父类的构造器;
例如:super(参数列表);
只能放在构造器的第一句,只能出现一句。
public class B extends A{
private String name;
public B(String name) {
this.name = name;
}
public B(String name, String name1) {
super(name);
this.name = name1;
}
public B(String name, int age, String name1) {
super(name, age);
this.name = name1;
}
}
3、super的优点
(1)分工明确,父类属性由父类初始化,子类的属性由子类初始化);
public PC(String cpu, int memory, int disk, String brand) {
super(cpu, memory, disk);//父类的属性调用父类构造器进行初始化
this.brand; //子类的特有属性由子类构造器初始化
}
(2)当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。
如果没有重名,使用super、this、直接访问是一样的效果(从子类逐层向上【父类】查找)!
现在有三个类,Son->Father->Grandpa
在Son类中调用hi()方法,顺序是:
1. 先找本类(Son)。如果有,并且可以调用,则调用。如果没有,则找父类。
2. 找父类(Father)。如果有,并且可以调用,则调用。如果没有,则找父类(Grandpa)。
3. ...一直往上找,直到↓
- 如果找到这个方法,但不能访问,则报错cannot access
- 如果找完了object都没找到,则提示方法不存在。
(3)super的作用是跳过子类,它的调用不限于访问直接父类。
如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员,如果多个基类(上级类)中都有同名的成员
使用super访问遵循就近原则。A(子类) -> B(父类) -> C(爷爷类)(先找父类,再找爷爷类,若都没有再找子类);
现在有三个类,Son->Father->Grandpa
1. Son中调用super.hi();
2. Father中没有hi();
3. 则看Grandpa,Grandpa中有hi();
4. 则上面的super.hi()调用的是Grandpa类中的hi();
====访问属性也是同样的道理====
4、super和this比较
区别点 | this | super |
---|---|---|
访问属性 | 从本类中开始查找 | 从父类开始查找 |
调用方法 | 从本类中开始查找 | 从父类开始查找 |
调用构造器 | this(形参列表) 必须放在构造器的首行 | super(形参列表) 必须放在子类构造器的首行 |
含义 | 表示当前对象 | 子类中访问父类对象 |
方法重写/覆盖(ovrride)
1、基本介绍
简单来说就是子类的方法和父类的方法一摸一样,那么就说子类的这个方法覆盖了父类的方法。
C->B->A,C中hi()和A中的hi()一样
那么就说C的hi()覆盖了A的hi()
方法覆盖会导致调用的时候,会只调用子类的hi()方法,不会调用父类的hi()方法
2、注意事项和使用细节
(1)子类的方法的形参列表,方法名称,要和父类方法的形参列表,方法名称完全一样;
(2)子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类;
比如:父类返回类型是Object,子类方法返回类型是String
class A {
public object getInfo(){}
}
class B extends A{
public String getInfo(){}
}
(3)子类不能缩小父类方法的访问权限,但可以扩大。
public > protected > 默认 > private
class A {
protected void getInfo(){}
}
class B extends A{
private void getInfo(){} //报错
public void getInfo(){} //通过
}
3、方法重写和重载比较
发生范围:重载在本类或者父子类中,重写必须发生在父子类中
方法能够在同一个类中或者在一个子类中被重载。根据实参的类型,判断到底是调用哪一个方法
public class Test {
public static void main(String[] args) {
Child c = new Child();
c.fun(1); //1father
c.fun(1.0); //1.0child
}
}
class Child extends Father{
public Child(){
}
public void fun(double n){
System.out.println(n+"child");
}
public void fun1(double n) {
System.out.println("b");
}
}
public class Father {
public Father() {
}
public void fun(int n){
System.out.println(n+"father");
}
}
Object类详解
Object类中主要的方法有:
-
equals(Object obj)
-
finalize()
-
getClass()
-
hashCode()
-
toString()
1、equals方法
(1)==和equals的对比
==是一个比较运算符:
① ==:既可以判断基本类型,又可以判断引用类型;
-
如果判断基本类型,判断的是值是否相等。例如:int i = 10; double d = 10.0;true
-
如果判断引用类型,判断的是地址是否相等,即判断是不是同一个对象;
④ equals():是Object类中的方法,只能判断引用类型;
- 默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如Integer,String。
(2)equals()练习
① 重写equals方法
public class Test {
public static void main(String[] args){
Person a = new Person("jack",20,'男');
Person b = new Person("jack",20,'男');
System.out.println(a.equals(b));
}
}
class Person {
private String name;
private int age;
private char gender;
public Person(String name,int age,char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public boolean equals(Object obj){
if(obj == this){
return true;
}
if(obj instanceof Person){
Person per = (Person) obj;
return this.name.equals(per.name) && this.age == per.age && this.gender == per.gender;
}
return false;
}
}
② 判断对错
int it = 65;
float fl = 65.0f;
System.out.println(it == fl); //true
char ch1 = 'A';
char ch2 = 12;
System.out.println(it == ch1); //true
System.out.println(12 == ch2); //true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); //false
System.out.println(str1.equals(str2)); //true
Person p = new Person("jack");
System.out.println("jack" == p); //编译错误
//总结:
//1. 判断基本数据类型时,==只用于判断值是否相等
//2. 判断引用数据类型时
//==用于判断地址,类型不相同时,编译不通过
//equals看是否重写,重写的是判断内容是否相等。默认String和基本类型包装类已经重写
//没有重写的,比如自定义类。则是判断地址
2、hashCode方法
hashCode值是jvm用随机数方式分配给各个对象的
- 两个引用,如果指向的是同一个对象,哈希值一定相等
- 两个引用,如果指向的是不同对象,则哈希值可能相同(概率小,但有可能发生)
- 哈希值不等价于地址,是通过地址经过某种转化而来
- hashCode在后面的集合中也需要重写
案例:
public class Test {
public static void main(String[] args){
AA aa = new AA();
AA aa2 = new AA();
AA aa3 = aa;
System.out.println("aa.hashCode()=" + aa.hashCode());
System.out.println("aa2.hashCode()=" + aa2.hashCode());
System.out.println("aa3.hashCode()=" + aa3.hashCode());
}
}
//输出:
aa.hashCode()=460141958
aa2.hashCode()=1163157884
aa3.hashCode()=460141958
3、toString方法
用于返回对象的属性信息
-
默认返回
全类名 + @ + 哈希值的十六进制
-
重写toString
public class Test {
public static void main(String[] args){
Monster monster = new Monster("li", "work", 2000);
System.out.println(monster.toString());
}
}
class Monster {
private String name;
private String job;
private int age;
public Monster(String name, String job, int age) {
this.name = name;
this.job = job;
this.age = age;
}
@Override
public String toString(){
return "name = " + this.name +
"\njob = " + this.job +
"\nage = " + this.age;
}
}
//输出
name = li
job = work
age = 2000
- 当直接输出一个对象时,toString方法会被默认的调用
比如:System.out.println(monster)
等价于:System.out.println(monster.toString());
4、finalize方法
与垃圾回收相关
- 当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作
- 什么时候被回收:某个对象没有任何引用,该对象被视为垃圾,会被回收
- 垃圾回收机制的调用,是由系统来决定(即有自己的GC算法),如果想立即运行垃圾回收器,可以通过
System.gc()
主动调用垃圾回收器
tips:实际开发中几乎不会用finalize,但作为知识点用来应付面试()
public class Test {
public static void main(String[] args){
Car bmw = new Car("宝马");
//如果程序员不重写finalize,则没什么区别
bmw = null;
//这时垃圾回收器视bmw为垃圾,但不一定马上销毁该对象
System.gc();
//主动触发垃圾回收
System.out.println("程序退出");
}
}
class Car {
private String name;
public Car(String name){
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("我们销毁 汽车" + name);
System.out.println("释放了某些资源");
}
}
//输出
程序退出
我们销毁 汽车宝马
释放了某些资源
重写equals方法必须重写hashcode方法
重复元素问题:一个集合中一个地址上,有相同的元素或者不同地址上有相同的元素
hash冲突:存储两个对象时,它们的hash值相同(需要存放到同一个地方)
- 如果只重写hashcode方法:发生hash冲突时,无法去除重复元素(即使两个对象内容相同,但因为地址不同,也会判断为不同对象,进而存储大量的重复对象)
- 如果只重写equals方法:那么相同的两个元素会出现在两个不同的地址上
不重写equals方法,则判断的是两个对象的地址是否相同。
重写equals方法,判断两个对象的内容是否相同。
只重写equals方法不重写hashcode方法,则equals判断为相同的两个对象,其hashcode值会不同(因为内容相同但地址不同)
此时这个对象类无法和集合类(hashMap、hashSet)一起工作
(因为hashMap、hashSet的工作机制是:
求取该对象的hashCode值
如果在列表key中没有找到对应的hashCode,那么直接添加该对象
如果找到了该hashCode值,再使用equals比较
如果equals比较内容相同,那么该对象覆盖到上面
如果equals比较内容不相同,那么该对象重新计算hashCode值,并散列到其他地址