super关键字
理解,父类的,可以调用属性,方法,构造器
super使用
在子类的方法或构造器中,通过使用“super.属性”或“super.方法”的方式,显式调用父类中声明的属性和方法。
通常情况下可以省略掉,但是子父类中出现同名属性时,为了区分,在子类中使用关键字super表示调用父类中的属性,this表示调用子类当中的属性,一般是父子类中属性赋值不同的情况。
this和super都可以调用属性,this调用的会先在本类中寻找属性,没找到会接着去父类当中去找到;而super调用的不会在本类当中寻找,直接到父类中找属性。
父子类中存在同名属性,在子类中想要调用父类的属性,用super调用,直接输出属性,意味着省略掉this调用当前类的属性
当子类重写了父类的方法以后,想在子类的方法中调用父类中被重写的方法,必须显式使用“super.方法”的方式,表明调用的是父类中声明的方法。
super调用构造器
- 在子类构造器中显式使用“super(形参列表)”的方式,调用父类中声明的指定的构造器;
- 该方式的使用,必须放在子类构造器的首行;
- 在类的构造器中,针对“this(形参列表)”,本类中构成重载的其他构造器,或“super(形参列表)”,父类中有参构造器, 只能二选一,不能同时出现;
- 在构造器的首行,没有显式的声明“this(形参列表) ”或“super(形参列表)”,默认调用的是父类中空参的构造器super().
- 在类的n多个构造器中,最多有n-1个构造器使用this(形参列表)的方式,至少有1个类的构造器中使用了“super(形参列表)”的方式来调用父类的构造器
子类对象实例化的过程
从结果上看:
子类继承父类后,就获取了父类中声明的属性和方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
从过程上看:(为什么出现以上的结果?)
当通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,(比如一个类多个构造器,this(形参列表)方式的调用,一定涉及到super调用父类的构造器,父类可能还有其父类,一层层这样,到达Object类) 进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。
调用父类构造器,父类构造器在父类当中,就需要把父类当中的结构给加载进内存,所以才可以看到内存中有父类的结构,子类获取了父类中声明的属性和方法,子类对象才可以考虑进行调用。
虽然创建子类对象时,调用了父类的构造器,父类构造器加载到了内存中,但是始终就创建过一个对象,就是new出来的子类对象。
多态性
理解: 一个事物的多种形态,父类的引用指向子类的对象。如Man,Woman是Person的子类,可以这样写 Person p = new Man(); Person p1 = new Woman();左边父类的声明,右边new个子类的对象
使用
当调用子类重写过的父类的同名同参方法时,实际执行的是子类重写父类的方法。
有了对象的多态性后,在编译期,只能调用等号左边父类中声明的方法,在运行期,实际执行的是右边子类重写父类的方法。
编译期看等号左边,运行时看等号右边
Person p = new Man(); 在代码中若出现p.earnMoney(),则直接报错,earnMoney()方法是Man类中有的方法,Man类重写了父类当中的eat()方法,p.eat(),最终显示的执行结果是子类中声明的eat方法。
多态性的使用前提
- 类的继承关系
- 要有方法的重写
为什么要有多态性
package com.csdn.day;
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Cat()); //Animal animal = new Cat();
test.func(new Dog());
}
public void func(Animal animal){
animal.eat();
animal.shout();
}
}
class Animal{
public void eat(){
System.out.println("动物进食");
}
public void shout(){
System.out.println("动物叫");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪汪汪");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵喵喵");
}
}
不使用多态的话,
package com.csdn.day;
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Cat());
test.func(new Dog());
}
public void func(Dog dog){
dog.eat();
dog.shout();
}
public void func(Cat cat){
cat.eat();
cat.shout();
}
}
有了多态性,省去了许多重载方法的设计;没有多态性,抽象类和接口就没有任何意义
对象的多态性,只适用于方法,不适用于属性,如Person类中int id = 1001; Man类中int id = 1002,在测试类中Person p = new Man(); sout(p.id); 输出值为1001.
new man对象,在堆空间中,有Person中结构包括id,有自身Man类的结构包括属性id,赋值不同,调用时编译和运行都看左边,声明为Person类型的,即调用Person中的属性
多态是运行时行为,而不是编译时行为。Person p = new Man(); 是比较明显的,知道子类类型,但比如说,Person p = getInstance(key); 通过一个方法以及参数随机获取子类类型,这只能是在运行时发现。
重载,存在多个同名方法,而方法参数不同,编译器根据方法不同的参数表,对同名的方法名称做修饰。对于编译器而言,这些同名方法就成了不同的方法,他们的调用地址在编译期就绑定了,java的重载是可以包括父类和子类的,比如说父类中有个eat()方法,子类中有eat(参数)方法,这仍然视为重载。
对于重载,在方法调用前,编译器就已经确定了所要调用的方法,成为“早绑定“或”静态绑定“,而多态只有等到方法调用那一刻,解释运行器才会确定所要调用的具体方法,称为”晚绑定“或”动态绑定“。 所以在涉及重载重写时,谈下重载不表现为多态性,重写表现为多态性,是加分项。
为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现?
无论通过哪个构造器创建子类对象,需要保证先初始化父类,目的是当子类继承父类后,继承父类中所有的属性和方法,子类有必要知道父类中如何为对象进行初始化。比如重写前提是先要拿到父类的方法,才能进行方法的覆盖
使用多态可以屏蔽子类之间的差异,写出更加通用的代码。
instanceof 关系运算符
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法相当于屏蔽掉了,不能调用。
package com.csdn.day;
public class Person {
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
package com.csdn.day;
public class Woman extends Person{
boolean isBeauty;
public void goShopping(){
System.out.println("女人喜欢购物");
}
public void eat(){
System.out.println("女人少吃");
}
public void sleep(){
System.out.println("女人多睡觉");
}
}
package com.csdn.day;
public class Man extends Person{
boolean isSmoking;
public void earnMoney() {
System.out.println("男人负责挣钱养家");
}
public void eat(){
System.out.println("男人要多吃饭");
}
public void sleep(){
System.out.println("男人休息时间少");
}
}
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Man();
//p1.earnMoney(); 不能调用子类特有的方法,属性,编译时,p1是Person类型的
System.out.println(p1);
//如何调用子类中特有的属性和方法
Man m1 = (Man) p1;
System.out.println(m1);
}
}
com.csdn.day.Man@1b6d3586
com.csdn.day.Man@1b6d3586
Process finished with exit code 0
基本数据类型转换,容量小的转换为容量大的,自动类型提升,如int转double,而容量大的要想转换为容量小的,须强制类型转换,如double 转int,但精度会有所损失。
在多态中,子类对象可以直接赋给父类如Person p = new Man(),可以称为向上转型,由父类如何转为子类相当于向下转型
内存中man对象的地址值赋给p1,地址值有类型@具体值,因为有类型的限制,想直接把它赋给m1,是不可以的,Man m1 = p1;在编译期就通不过,把p1看作Person类型,强转后,类型变换,具体值仍然不变,p1和m1指向同一内存区域,m1声明为man,即可调用自己的方法和属性
写强制类型转换的,可能会执行出现异常,如Woman w = (Woman)p1; w.goShopping();运行程序出现ClassCastException异常,说是Man类型的不能强转为Woman类型的,instanceof的出现就是为了避免出现这样的问题。
instanceof使用
a instanceof A: 判断对象a是否是类A 的实例,是的话,返回true,不是返回false.
if(p1 instanceof Man){
Man man = (Man)p1;
man.earnMoney();
System.out.println("man");
}
if(p1 instanceof Woman)返回false
为了避免在向下转型时出现异常,在向下转型时,先进行instanceof的判断,一旦返回true,就进行向下转型,返回false,不进行向下转型。
如果a instanceof A返回true,则 a instanceof B 也返回true,那么,类B 是类A 的父类。如下,另外任何一个类的实例都可以作为Object类的实例出现
if(p1 instanceof Person){
System.out.println("person");
}
if(p1 instanceof Object){
System.out.println("object");
}
反过来,如果 a instanceof B 返回true,那么a instanceof A 不一定返回true.
若子类重写了父类方法,意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中,编译看左边,运行看右边;
对于实例变量则不存在这样的问题,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可以覆盖父类中的实例变量,编译运行都看左边。
获取一个类的父类,创建对象;对象名.getClass().getSuperclass() .
Object类只声明了一个空参的构造器
垃圾回收机制回收任何对象之前,都会先调用它的finalize方法,永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用
== 和equals()的区别
== 运算符
- 基本数据类型和引用数据类型中都可以使用,基本数据类型除了boolean外,其它七种可以进行比较,不同类型的也可以比较,自动类型提升。
- 比较的是基本数据类型的话,比较两个变量保存的数据是否相等
- 比较引用数据类型,比较的是地址值是否相等,两个引用是否指向同一个对象实体,堆空间中同一个对象
Customer cust1 = new Customer("John",21);
Customer cust2 = new Customer("John",21);
sout(cust1 == cust2); //false
String str1 = new String("hello");
String str2 = new String("hello");
sout(str1 == str2); //false
equals()是个方法
- 只能使用于引用数据类型
- 具体要看自定义类里有没有重写Object的equals方法来判断。
sout(cust1.equals(cust2)); //false
sout(str1.equals(str2)); //true
java.lang.Object类中的equals方法内部定义实际和 == 作用是一样的,可看源码,比较地址值;像String,Date,File,包装类等都重写了Object类里的equals(),重写以后,比较的是对象的实际内容是否相同。
自定义类重写equals()方法,通常也是想比较两个对象的实际内容是否相同,需要对Object类中的equals方法进行重写,手动实现
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(obj instanceof Customer){
Customer cust = (Customer)obj;
if(this.age == cust.age && this.name.equals(cust.name)){
return true;
}else{
return false;
}
}
return false;
}
}
Object类中toString()使用
当输出一个对象的引用时,相当于默认调用了对象的toString()方法
Object类中ToString方法的定义,地址数值hashCode算出来的。
像StrIng,Date,File等重写了Object类的toString()方法,在调用时,返回的是”实体内容“的信息。
自定义类可以重写toString方法。
java中的Junit单元测试,java类要求,此类是public的,此类提供无参构造器;
单元测试方法,方法权限是public,没有返回值,没有形参
包装类
java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
除了Boolean,Character之外,其他的数值型包装类的父类为java.lang.Number。
基本类型,包装类,String类之间的转换
基本数据类型,包装类间的转换
调用包装类的构造器创建包装类对象
int num = 10;
Integer in1 = new Integer(num);
sout(in1);
Integer in2 = new Integer("234");
sout(in2);
Float f1 = new Float(12.3f);
Float f2 = new Float("12.3");
System.out.println(f1);
System.out.println(f2);
Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean("TrUe");
System.out.println(b1);
System.out.println(b2);
Boolean b3 = new Boolean("true123");
System.out.println(b3);
public static boolean parseBoolean(String s) {
return ((s != null) && s.equalsIgnoreCase("true"));
}
10
234
12.3
12.3
true
true
false
class Order{
boolean isMale;
Boolean isFemale;
}
Order order = new Order();
sout(order.isMale); //false
sout(order.isFemale); //null
包装类默认是Null,而基础数据类型是有默认值的
Boolean定义的变量未显式声明值,则为null,boolean定义的变量的默认初始值为false
包装类转换为基本数据类型,拆箱
调用包装类的xxxValue()
Integer integer = new Integer(12);
int m = integer.intValue();
Float float = new Float(12.3);
float f = float.floatValue();
JDK5.0新特性之后,自动装箱拆箱
//自动装箱,取代包装类使用构造器创建包装类对象
int num = 100;
Integer m = num;
boolean b1 = true;
Boolean b2 = b1;
//自动拆箱
int num = m;
基本数据类型,包装类,String类型之间转换
基本数据类型,包装类转换String类型
连接运算
int num = 10;
String str = num + "";
调用String类的静态方法valueOf
float f1 = 12.3f;
String str2 = String.valueOf(f1);
Double d = new Double(12.4);
String str3 = String.valueOf(d);
String类型转换为基本数据类型,包装类
调用包装类的parseXXX(String s)
String str = "123";
int num = Integer.parseInt(str);//可能会出现NumberFormatException异常。
String str1 = "true";
boolean b1 = Boolean.parseBoolean(str2);
Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],保存了从-128127范围的整数,如果使用自动装箱的方式,给Integer赋值的范围在-128127的范围时,可以直接使用数组中的元素,不用再去new了。出了这个范围的相当于new了一个Integer对象
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); //false,==在引用数据类型中是判断地址值是否相同
Integer m = 1;
Integer n = 1;
System.out.println(m == n); //true
Integer x = 128;
Integer y = 128;
System.out.println(x == y); //false
有些方法的形参就是Object类型的,把int等基本数据类型作为参数,是不行的,必须要以包装类的方式才能放进去。如在使用集合类型时,就一定要使用包装类,因为容器都是装Object类型的,基本数据类型显然不适用。
java是面向对象的编程语言,基本数据类型并不具备对象的性质,在实际生活中存在很多的不便。为了让基本数据类型也拥有对象的特征,就出现了包装类型,相当于将基本类型”包装起来“,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本数据类型的操作。