this关键字调用构造器
public class Leaf { private int i = 0; private int j = 0; public Leaf() { } public Leaf(int i, int j) { // 使用this关键字调用无参构造函数,位于首行 this(); this.i = i; this.j = j; } }
this关键字概述:
① 在类的方法定义中使用的 this 关键字代表使用该方法的对象的引用。
② 当必须指出当前使用方法的对象是谁时要使用 this
③ 有时使用 this 可以处理方法中成员变量和参数重名的情况。
④ this可以看作是一个变量,它的值是当前对象的引用
public class Leaf { int i = 0; public Leaf(int i) { this.i = i; } Leaf increament() { i++; return this; } void print() { System.out.println("i = " + i); } public static void main(String[] args) { Leaf leaf = new Leaf(1000); leaf.increament().increament().print(); } }
this是一个引用,它指向自身的这个对象。
看内存分析图:
假设我们在堆内存new了一个对象,在这个对象里面你想象着他有一个引用this,this指向这个对象自己,所以这就是this,这个new出来的对象名字是什么,我们不知道,不知道也没关系,因为这并不影响这个对象在内存里面的存在,这个对象只要在内存中存在,他就一定有一个引用this。
看下面的例子分析:
package cn.galc.test; public class Leaf { int i = 0; public Leaf(int i) { this.i = i; } Leaf increament() { i++; return this; } void print() { System.out.println("i = " + i); } public static void main(String[] args) { Leaf leaf = new Leaf(100); leaf.increament().increament().print(); } }
在内存中分析main方法的执行过程
首先分析第一句话:Leaf leaf = new Leaf(100);
程序执行到这里的时候,栈空间里面有一个变量 leaf,它指向了我们 new 出来的在堆空间里面的 Leaf 对象。new 这个 Leaf 对象的时候,调用了构造方法 Leaf(), 这个构造方法里面有一个形参 i,所以在栈空间里面给构造方法分配有一小块内存,名字叫i用来装传递过来的实参。这里传过来的实参是100,所以i里面装的值就是100。得到这个值之后,构造方法继续执行,执行 this.i = i
;这里就是把栈空间里面的i的值通过值传递给 Leaf 对象里面的成员变量 i,所以成员变量i的值也变成了100。内存中的布局如下图所示:
构造方法执行完之后,为这个构造方法分配的内存消失,所以栈里面的i所标记的那一小块内存会消失。因此第一句话执行完之后,内存中的布局如下图所示:
接下来分析第二句话:leaf.increament().increament().print();
首先逐个分析:leaf.increament()
,这里是调用 increament()
方法,是对 new 出来的那个 Leaf 对象调用的,leaf 是Leaf 对象的引用对象,因此通过这个引用对象来调用 increament()
方法,即相当于是 Leaf 对象自己调用了increament()
方法。increament()
方法的定义如下:
Leaf increament(){ i++; return this; }
因此Leaf对象调用 increament()
方法时,首先执行方法体里面的第一句话 i++
;这样就把 Leaf 对象的成员变量 i 的值由原来的100变成了101。此时的内存布局如下图所示。
接下来执行方法体里面的第二句话:return this;
这里把 this 作为返回值,当有返回值的时候,首先会在栈里面给这个返回值分配一小块临时的存储空间。这块存储空间里面的内容是this里面的内容。this 指向它自身,所以栈内存里面的那块临时存储空间里面装的 this 也是指向堆内存里面的 Leaf 对象。
所以 leaf.increament().increament().print();
这句话里面的 left.increament()
这一小句话执行完之后,内存中的布局如下图所示。
leaf.increament().increament().print();
这句话里面的 left.increament()
这一小句话执行完之后,返回一个 this,此时leaf.increament().increament().print();
就相当于是 this.increament().print();
接着栈里面的存储在临时空间里面的 this 调用 increament()
方法,而 this 指的就是 Leaf 对象,所以又是 Leaf 对象调用 increament()
方法。Leaf 对象调用 increament()
方法时,又会执行方法体里面的 i++,所以此时i又由原来的101变成了102。然后又执行 return this
,所以栈内存里面又多了一块临时存储空间,里面装的值也是 this,这个 this 又是指向堆内存里面的 Leaf 对象。因此此时这个 Leaf 对象有了四个指向他自己的引用对象。
leaf.increament().increament().print();
这句话里面的 leaf.increament().increament()
这一小句话执行完之后,都返回了一个 this,所以此时的 leaf.increament().increament().print();
就相当于是这样子的:this.this.print();
接下来又是栈里面的那个新的 this 调用 print()
方法,使用 this 来调用,那就相当于是 Leaf 对象来调用,Leaf 对象自己调用 print() 方法将自己的i属性的值打印出来,所以打印出来的结果应该是102。
因此main方法里面的整个程序执行完之后,内存中的布局如下图所示:
this的总结:this一般出现在方法里面,当这个方法还没有调用的时候,this指的是谁并不知道。但是实际当中,你如果new了一个对象出来,那么this指的就是当前这个对象。哪个对象调用方法,this指的就是调用方法的这个对象(你对哪个对象调用这个方法,this指的就是谁)。如果再new一个对象,这个对象他也有自己的this,他自己的this就当然指的是他自己了。
当this 调用构造器时必须位于首行。
this 表示当前对象。super表示当前对象的父类在内存空间中的位置,不是对象,是一个地址值。
1、在子类中使用super来调用父类中的指定操作:
① super可用于访问父类中定义的属性
② super可用于调用父类中定义的成员方法
③ super可用于在子类构造器中调用父类的构造器
3、super调用父类的构造器:super();
① 子类中所有的构造器默认都会访问父类中空参的构造器
②当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行
③ 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错
调用父类构造器举例
public class Person { private String name; private int age; private Date birthDate; public Person(String name, int age, Date d) { this.name = name; this.age = age; this.birthDate = d; } public Person(String name, int age) { this(name, age, null); } public Person(String name, Date d) { this(name, 30, d); } public Person(String name) { this(name, 30); } } public class Student extends Person { private String school; public Student(String name, int age, String s) { super(name, age); school = s; } public Student(String name, String s) { super(name); school = s; } // 编译出错: no super(),系统将调用父类无参数的构造器。 public Student(String s) { school = s; } }
区别点 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
1、什么是多态?
引入 : 生活中的多态性! 你自己的身份是学生,你的身份职场精英,患者.在不同的时期不同环境,状态是不同的.
生活中的多态性: 一个事物具备的不同形态.
多态性,是面向对象中最重要的概念,在Java中的体现为对象的多态性,即父类引用指向子类的对象。
多态的语法规则: 父类或者接口的引用指向自己的子类的对象
父类 变量(对象名) = new 子类对象(); //多态写法
对象调用方法,执行的是子类的方法重写
多态中成员变量的特点
多态中成员方法的特点
简练 : 成员方法编译看左边,运行看右边.成员变量都是左边
如果没有形成多态,即子类中没有重写父类的方法。那么运行时,就运行父类的方法。
Person p = new Student();
public class Person { String s = "父类成员"; public void eat(){ System.out.println("人在吃饭"); } }
public class Student extends Person { String s = "子类成员"; public void eat(){ System.out.println("学生吃饭"); } }
public static void main(String[] args) { Person p = new Student(); //对象p,子类对象,调用成员变量s System.out.println(p.s); //子类对象调用方法 p.eat(); }
问题:在多态的程序中,不能调用子类的特有成员,只能调用子类父类的共有成员。
为了解决上述问题,于是有了多态的向下转型。
格式:
转后类型 变量名 = (转后类型)要转的数据; //公式
public static void main(String[] args) { //创建对象,多态性 //父类 = new 任意子类对象() 扩展 Animal animal = new Cat(); animal.eat(); //Cat类的特有功能 catchMouse()方法 //类型转换,强制 //Cat提升为了Animal,转回Cat类型 Cat c = (Cat)animal; c.catchMouse(); }
图解:
补充: 多态性提升扩展性,是否需要强制转换,根据实际功能需求来决定.
在多态的向下转型中经常会发生 ClassCastException 类型转换异常
我们现在有一个案例:
public class Animal{} public class Dog extends Animal{} public class Cat extends Animal{} public static void main(String[] args) { Animal animal = new Cat(); Dog dog = (Dog) animal; // 编译不会报错,但是运行后会报错:Cat类不能准换为Dog类 }
在运行上面的代码后,会报一个错:ClassCastException:xxx.Cat cannot be cast to xxx.Dog
java提供了一个关键字来解决这个异常:instanceof
,其格式及含义如下:
对象名 instanceof 类的名字 解析: 比较这个对象,是不是由这个类产生的(返回值为boolean类型) c instanceof Cat 解释: c对象是不是Cat类产生的,如果是结果就是true
强制类型转换之前的安全性判断
public static void main(String[] args) { //多态创建对象 Animal animal = new Dog(); animal.eat(); //调用子类的特有方法 //判断 animal是不是Cat类的对象 if (animal instanceof Cat){ //if为true,强制转换为Cat Cat c = (Cat)animal; c.catchMouse(); } //判断 animal是不是Dog类的对象 if (animal instanceof Dog){ Dog d = (Dog)animal; d.lookHome(); } }
1.关于A b = new B(),B是A的子类,调用b.eat()是怎么运作的?
答:编译时根据左边部分来编译,即根据类A来编译,若A有eat()方法,则编译通过,若A没有eat()方法,则编译失败。运行时具体是调用A的eat()方法,还是调用B的eat()方法,则根据静态绑定或动态绑定来判断,若是静态绑定,则编译时就确定了是调用A的eat()方法,若是动态绑定,则在运行时才能确定,若B有eat()方法,则调用B的eat()方法,若B没有eat()方法,则调用A的eat()方法。
2.只有动态绑定体现了多态,静态绑定是没有体现多态的。
3.private方法、static方法、final方法是静态绑定的,其他则是动态绑定。
4.静态绑定:指编译的时候就知道调用的是父类还是子类的方法。如A、B类,B类是A的子类,A、B类都有static的chi()方法,A b = new B(),调用b.chi(),编译的时候会检查A类是否有chi()方法,有则编译通过,没有则编译不通过。如果编译通过了,因为static是静态绑定的,所以JVM知道b.chi()是调用的A类的chi()方法,编译的时候就确定了调用的是A类的chi()方法,然后运行的时候就会调用A类的chi()方法;
5.动态绑定:编译的时候是不知道调用父类还是子类的方法,运行的时候才能确定。如A b = new B(),调用b.eat(),B是A的子类,eat()方法是public的成员方法,编译是根据左边部分编译的,编译会先检查A类是否有eat()方法,有则编译通过,没有则编译不通过。如果编译通过了,然后运行的时候是根据右边部分来的,会检查B类是否有eat()方法,有则调用B类的eat()方法,没有则调用A类的eat()方法。
6.为什么静态方法不能重写?
首先我们要知道重写的目的是什么,重写的目的就是为了实现多态,使一个父类的不同的子类在某个方法上展现出不同的行为,明白了这个之后,我们再看看什么是静态方法,主要用静态方法来干什么,毋庸置疑,静态方法是属于父类及其所有子类的,它就是被用来当作多个类的工具方法,它属于多个类的所有对象,它是一个公用的方法,任何对象都可以使用它,任何对象都有这个共有的行为,所以静态方法不需要在不同的对象上有不同的行为,因此,从设计的角度看,它就不需要多态,也就是不需要重写,因此,它就被规定不能被重写。
这也能解释为什么把static方法设计成静态绑定。
7.为什么静态方法能被继承?
从第6条,我们知道static方法就是一个所有对象公用的一个工具类,既然父类有该工具方法,那么子承父业,子类自然也可有该工具方法,子类的所有对象都可使用该工具方法(子类的所有对象都具有该共有的行为)。
8.为什么声明的父类引用变量指向子类对象时无法调用子类特有的方法?
举个例子就知道了:
如A b = new B(),b.eat();B是A的子类,eat()是B特有的方法,A类没有,编译的时候是根据左边部分编译的,编译时JVM会检查A类是否有eat()方法,显然A类是没有eat()方法的,所以,编译就无法通过,自然无法调用子类特有的方法。
9.有类A,B,B是A子类,两个类中都有一个私有的成员方法eat(),且两个类中的eat方法一摸一样时,如A b = new B(),调用b.eat()是什么情况?
我们先来按正常思路想一下:那就是编译的时候是按左边部分编译的,那就是按A类编译的,会先检查A类是否有eat()方法,明显A类是有eat()方法的,所以编译通过,而且private方法是静态绑定的,所以在编译时期就确定了在运行的时候时调用A类的eat()方法,我们想当然就认为程序能正常运行,但是还有一点我们没有考虑到,那就是private表示方法只能在本类被调用,故结果是编译不通过。
10.private方法为什么是静态绑定的?
很简单,private是私有的,不能被继承,自然不能被重写,自然就没有多态,故设计成静态绑定的。
11.final方法为什么是静态绑定的?
也很简单,final表示不可变的,加在方法上,表示方法是不可变的,方法不可变,那就意味着方法不能被重写,进一步就可知道没有多态,自然就被设计成静态绑定了。
方法名称 | 类型 | 描述 |
---|---|---|
public Object() | 构造 | 构造器 |
public boolean equals(Object obj) | 普通 | 对象比较 |
public int hashCode() | 普通 | 取得Hash码 |
public String toString() | 普通 | 对象打印时调用 |
当子类调用object类中的方法时通常会根据自己的需要去重写这些方法,如果不重写,就默认调用object类中的方法。
① equals():所有类都继承了Object,也就获得了equals()方法。还可以重写。
② 特例:当用equals()方法进行比较时,对类File、String、Date及包装类来说,是比较类型及内容而不考虑引用的是否是同一个对象。
③ 当自定义使用equals()时,可以重写。用于比较两个对象的“内容”是否都相等。
④ ==与equals()的区别
1、== 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
2、equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
3、具体要看自定义类里有没有重写Object的equals方法来判断。
4、通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
① toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
② 在进行String与其它类型数据的连接操作时,自动调用toString()方法
Date now=new Date(); System.out.println(“now=”+now); 相当于 System.out.println(“now=”+now.toString());
③ 可以根据需要在用户自定义类型中重写toString()方法
如String 类重写了toString()方法,返回字符串的值。
s1=“hello”; System.out.println(s1);//相当于System.out.println(s1.toString());
④ 基本类型数据转换为String类型时,调用了对应包装类的toString()方法
int a=10; System.out.println(“a=”+a);
抽象的概念 : 凡是说不清楚的都是抽象
例子 : 我买了一台手机,买了一直笔,都是抽象概念.
具体: 华为Meta40Pro ,金属, 16G+512
程序中 : 我知道这个功能存在,但是怎么完成就说不清楚,程序中也出现了抽象.
使用关键字 abstract定义抽象方法
权限修饰符 abstract 返回值类型 方法名字(参数列表) ; abstract关键字 抽象方法没有方法体, 不需要{},直接分号结束
当一个类中的方法是抽象方法的时候,这个类必须是抽象类,在类的关键字class前面使用abstract修饰.
public abstract class 类名{}
public abstract class Animal { /** * 动物吃什么? * 说不清楚,抽象,可以不说 */ public abstract void eat(); }
① 抽象类不能实例化对象
② 使用抽象类需要创建一个子类继承这个抽象类,然后重写抽象类中的所有抽象方法。如果仅重写抽象类中的部分抽象方法,那么这个子类也需要声明为抽象类。
③ 使用多态创建对象,调用子类中重写的方法。
public abstract class Animal { public abstract void eat(); }
public class Cat extends Animal{ /** * 重写父类的方法 * 去掉修饰符 abstract * 添加主体 {} */ @Override public void eat(){ System.out.println("猫吃鱼"); } }
public static void main(String[] args) { //创建Animal的子类对象 Animal animal = new Cat(); //eat方法不可能执行父类,运行子类的重写 animal.eat(); }
抽象类中可以定义成员变量。属性私有,set/get,有子类对象调用。
public abstract class Animal { //抽象类中能否定义成员变量 private String name; public abstract void eat(); public String getName() { return name; } public void setName(String name) { this.name = name; } }
public static void main(String[] args) { Animal animal = new Cat(); animal.eat(); //animal对象调用方法 get/ set animal.setName("tom"); String name = animal.getName(); System.out.println(name); }
抽象类中有构造方法,不写有默认的
public abstract class Animal { public Animal(){ System.out.println("Animal的构造方法"); } public Animal(String name){ this.name = name; System.out.println("有参数String的构造方法"); } //抽象类中能否定义成员变量 private String name; public abstract void eat(); public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class Cat extends Animal { public Cat(){ //调用父类的有参数构造方法 super("张三"); } @Override public void eat() { System.out.println("猫吃鱼"); } }
抽象类中可以不定义抽象方法,但是一旦类中出现抽象方法,那么这个类必须声明为抽象类。
身边的接口有哪些,笔记本上USB接口,HDMI,TypeC接口,插座
USB接口 : 连接鼠标,键盘,摄像头,手机,移动硬盘,电风扇.设备的工作原理不同,但是都可以连接到USB接口上,完成他的任务.说明了一个问题 : 这些设备都满足USB的接口规范!!
接口:就是一个规范,或者称为标准 , 无论什么设备,只要符合接口标准,就可以正常使用.
接口的扩展性很强大.
当一个抽象类中的方法都是抽象方法的时候,我们就给这样的抽象类起了一个名字:接口。因此,接口是特殊的抽象类。
定义接口需要用到关键字:interface
语法规范:
public interface 接口名{}
接口在编译后,依然还是.class文件
成员变量的定义具有固定格式
成员变量的修饰符是固定为:public static final
// 命名规范public static final 变量类型 变量名 = 值;
成员方法的定义具有固定格式
成员方法的修饰符固定为:public abstract
public abstract 返回值类型 方法名(参数列表) ;
① 接口不能实例化,即不能new
对象
② 需要定义一个类去实现接口,才能使用接口
implements
class 类名 implements 接口名{}
③ 接口的实现类需要重写接口中的所有抽象方法
案例:
/** * 定义好的接口 */ public interface MyInterFace { //接口的成员变量 public static final int A = 1; //接口的成员方法 public abstract void myInter(); }
/** * 定义MyInterFace接口的实现类 * 重写接口的抽象方法 */ public class MyInterFaceImpl implements MyInterFace{ public void myInter(){ System.out.println("实现类实现接口,重写方法"); } }
public static void main(String[] args) { //创建对象,多态性,创建接口实现类的对象 MyInterFace my = new MyInterFaceImpl(); my.myInter(); //输出接口中的成员A的值 System.out.println(my.A); }
类和类之间只允许单继承,而接口的出现是对单继承的改良,接口允许一个类同时实现多个接口。
语法格式:
class 类名 implements 接口A,接口B{}
案例:
接口的实现类,重写实现的多个接口中的所有抽象方法
public interface A { public abstract void a(); }
public interface B { public abstract void b(); }
/** * 实现接口A和B */ public class C implements A,B{ @Override public void a() { System.out.println("重写A接口方法"); } @Override public void b() { System.out.println("重写B接口方法"); } }
public static void main(String[] args) { C c = new C(); c.a(); c.b(); }