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方法。

多态性的使用前提

  1. 类的继承关系
  2. 要有方法的重写

为什么要有多态性

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是面向对象的编程语言,基本数据类型并不具备对象的性质,在实际生活中存在很多的不便。为了让基本数据类型也拥有对象的特征,就出现了包装类型,相当于将基本类型”包装起来“,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本数据类型的操作。