this,super,final,instanceof 关键字

Java this 关键字

this关键字

在Java中,this关键字用于引用方法或构造函数中的当前对象。

例如:

class Main {
    int instVar;

    Main(int instVar){
        this.instVar = instVar;
        System.out.println("this引用= " + this);
    }

    public static void main(String[] args) {
        Main obj = new Main(8);
        System.out.println("对象引用= " + obj);
    }
}
输出:
this引用= com.ThisAndThat.MyClass@74a14482
对象引用 = com.ThisAndThat.MyClass@74a14482

在上面的示例中,我们创建了Main类的一个名为obj的对象。然后,我们打印对类的对象obj和this关键字的引用。

在这里,我们可以看到obj和this的引用是相同的。这意味着这只是对当前对象的引用

使用this方法处理变量名的歧义性

在Java中,不允许在范围(类范围或方法范围)内声明两个或多个具有相同名称的变量。但是,实例变量和参数可能具有相同的名称。例如,

class MyClass {
    // 实例变量
    int age;

    // 参数
    MyClass(int age){
        age = age;
    }
}

在上面的程序中,实例变量和参数具有相同的名称:age。在这里,由于名称不明确,Java编译器感到困惑。

在这种情况下,我们使用this关键字。例如,

首先,让我们看一个不使用this关键字的示例:

class Main {

    int age;
    Main(int age){
        age = age;
    }

    public static void main(String[] args) {
        Main obj = new Main(8);
        System.out.println("obj.age = " + obj.age);
    }
}
输出:
mc.age = 0

在上面的示例中,我们已将一个值8传递给了构造函数。但是,我们得到的0是输出。这是因为Java编译器由于实例变量和参数之间的名称不明确而感到困惑。

现在,让我们使用this关键字重写上面的代码。

class Main {

    int age;
    Main(int age){
        this.age = age;
    }

    public static void main(String[] args) {
        Main obj = new Main(8);
        System.out.println("obj.age = " + obj.age);
    }
}
输出:
obj.age = 8

现在,我们得到了预期的输出。这是因为当构造函数被调用时,构造函数内部的内容被调用构造函数的对象obj所替换。因此,age变量被赋值为8。

另外,如果参数和实例变量的名称不同,则编译器会自动附加this关键字。例如代码:

class Main {
    int age;

    Main(int i) {
        age = i;
    }
}
等同于:
class Main {
    int age;

    Main(int i) {
        this.age = i;
    }
}

this 与Getters和Setters

this关键字的另一个常见用法是在类的setter和getter方法中。例如:

class Main {
   String name;

   // setter 方法
   void setName( String name ) {
       this.name = name;
   }

   // getter 方法
   String getName(){
       return this.name;
   }

   public static void main( String[] args ) {
       Main obj = new Main();

       // 调用setter和getter方法
       obj.setName("Seagull");
       System.out.println("obj.name: "+obj.getName());
   }
}
输出:
obj.name: Seagull
在这里,我们使用了this关键字:
• 在setter方法中分配值
• 在getter方法中访问值

在构造函数重载中使用this

在处理构造函数重载时,我们可能必须从另一个构造函数调用一个构造函数。在这种情况下,我们不能显式调用构造函数。相反,我们必须使用this关键字。

在这里,我们使用this关键字的另一种形式。也就是this()。让我们举个实例

class Complex {

    private int a, b;

    //带两个参数的构造函数
    private Complex( int i, int j ){
        this.a = i;
        this.b = j;
    }

    //具有单个参数的构造函数
    private Complex(int i){
        //用两个参数调用构造函数
        this(i, i); 
    }

    //没有参数的构造函数
    private Complex(){
        //用单个参数调用构造函数
        this(0);
    }

    @Override
    public String toString(){
        return this.a + " + " + this.b + "i";
    }

    public static void main( String[] args ) {
  
                //创建Complex类的对象
                //使用2个参数调用构造函数 
        Complex c1 = new Complex(2, 3); 
    
        //使用单个参数调用构造函数
        Complex c2 = new Complex(3);

        //不带参数调用构造函数
        Complex c3 = new Complex();

        //打印对象
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
    }
}
输出:
2 + 3i3 + 3i0 + 0i
在上面的示例中,我们使用了this关键字,
• 从构造函数Complex(inti)调用构造函数Complex(inti,intj)
• 从构造函数Complex()调用构造函数Complex(int i)

注意这一行,

System.out.println(c1);

在这里,当我们打印对象c1时,对象被转换成字符串。在此过程中,将调用toString()。由于我们在类中重写了toString()方法,因此我们根据该方法获得输出。

this()的最大优点之一是减少了重复代码的数量。但是,在使用this()时,我们应该特别小心。这是因为从另一个构造函数调用构造函数会增加开销,而且这是一个缓慢的过程。使用this()的另一个巨大优势是减少重复代码的数量。

注意:从另一个构造函数调用一个构造函数称为显式构造函数调用。

this作为参数传递

我们可以使用this关键字将当前对象作为参数传递给方法。例如,

class ThisExample {
    // declare variables
    int x;
    int y;

    ThisExample(int x, int y) {
          //为构造函数内的变量赋值
        this.x = x;
        this.y = y;

        //调用add()前x和y的值
        System.out.println("在将this其传递给addTwo()方法之前:");
        System.out.println("x = " + this.x + ", y = " + this.y);

        //调用add()方法,将this其作为参数传递
        add(this);

        //调用add()后x和y的值
        System.out.println("在将this其传递给addTwo()方法之后:");
        System.out.println("x = " + this.x + ", y = " + this.y);
    }

    void add(ThisExample o){
        o.x += 2;
        o.y += 2;
    }
}

class Main {
    public static void main( String[] args ) {
        ThisExample obj = new ThisExample(1, -2);
    }
}
输出:
在将this其传递给addTwo()方法之前:
x = 1, y = -2
在将this其传递给addTwo()方法之后:
x = 3, y = 0

在上面的示例中,在构造函数ThisExample()中,注意下面的一行,

add(this);在这里,我们通过将this作为参数传递来调用add()方法。因为this关键字包含对类的对象obj的引用,所以我们可以在add()方法中更改x和y的值。this存储的是所在方法调用者的地址值。


Java super 关键字

Java中的super关键字在子类中用于访问父类成员(属性,构造函数和方法)。

在学习super关键字之前,需了解Java继承

super关键字的使用

  1. 调用子类中重写的父类的方法。

  1. 如果超类(superclass)和子类(subclass)都有同名的属性,则访问超类的属性(字段)。

  1. 从子类构造函数显式地调用超类无参数化构造函数或参数化构造函数。

1.访问超类的重写方法

如果在超类和子类中都定义了相同名称的方法,则子类中的方法将覆盖超类中的方法。这称为方法重写

class Animal {

  //方法
  public void display(){
    System.out.println("I am an animal");
  }
}

class Dog extends Animal {

  //重写方法
  @Override
  public void display(){
    System.out.println("I am a dog");
  }

  public void printMessage(){
    display();
  }
}

class Main {
  public static void main(String[] args) {
    Dog dog1 = new Dog();
    dog1.printMessage();
  }
}
输出结果
I am a dog

在本示例中,通过创建Dog类的对象dog1,我们可以调用它的方法printMessage(),然后该方法执行display()语句。

由于display()在两个类中都定义,所以子类Dog的方法覆盖了超类Animal的方法。因此,调用了子类的display()。

如果必须调用超类的重写方法怎么办?

如果需要调用超类Animal的重载方法display(),则使用super.display()。

super调用超类方法
class Animal {

  //方法
  public void display(){
    System.out.println("I am an animal");
  }
}

class Dog extends Animal {

  //重写方法
  @Override
  public void display(){
    System.out.println("I am a dog");
  }

  public void printMessage(){

    //这调用重写方法
    display();

    // 这调用父类的方法
    super.display();
  }
}

class Main {
  public static void main(String[] args) {
    Dog dog1 = new Dog();
    dog1.printMessage();
  }
}
输出结果
I am a dog
I am an animal

在这里,上述程序是如何工作的。

2.访问超(父)类的属性

超类和子类可以具有相同名称的属性。我们使用super关键字来访问超类的属性。

访问超类属性
class Animal {
  protected String type="动物";
}

class Dog extends Animal {
  public String type="哺乳动物";

  public void printType() {
    System.out.println("我是 " + type);
    System.out.println("我是一只 " + super.type);
  }
}

class Main {
  public static void main(String[] args) {
    Dog dog1 = new Dog();
    dog1.printType();
  }
}
输出:
我是哺乳动物
我是一只动物

在这个实例中,我们在超类Animal和子类Dog中定义了相同的实例字段类型。

然后我们创建了Dog类的对象dog1。然后,使用此对象调用printType()方法。

在printType()函数内部,

  • type - 指的是子类Dog的属性。

  • super.type - 指超类Animal的属性。

因此,System.out.println("我是 " + type);输出 “我是哺乳动物”,

并且,System.out.println("我是一只 " + super.type);打印输出“我是一只动物”。

3.使用super()访问超类构造函数

众所周知,创建类的对象时,将自动调用其默认构造函数。

要从子类构造函数中显式调用超类构造函数,我们使用super()。这是super关键字的一种特殊形式。

注意:super() 只能在子类构造函数中使用,并且必须是第一条语句。

super()访问超类构造函数
class Animal {

  //Animal类的默认或无参数构造函数
  Animal() {
    System.out.println("I am an animal");
  }
}

class Dog extends Animal {

  // Dog类的默认或无参数构造函数
  Dog() {

    //调用超类的默认构造函数
    super();

    System.out.println("I am a dog");
  }
}

class Main {
  public static void main(String[] args) {
    Dog dog1 = new Dog();
  }
}
输出结果
I am an animal
I am a dog

在这里,当Dog类的对象dog1被创建时,它会自动调用该类的默认或无参数构造函数。

在子类构造函数中,super()语句调用超类的构造函数并执行其中的语句。因此,我们得到的结果“I am an animal”

然后,程序流返回到子类构造函数并执行其余语句。 因此,打印输出“I am a dog”。

但是,不是必须使用super()。 即使在子类构造函数中没有使用super(),编译器也会隐式调用超类的默认构造函数。

那么,如果编译器自动调用super(),为什么还要显式它,而使用冗余代码呢?

如果必须从子类构造函数中调用超类的参数化构造函数(带有参数的构造函数),则必须显式地使用它。

带参数的super()必须始终是子类的构造函数体中的第一个语句,否则,将出现编译错误。

使用super()调用参数化构造函数
class Animal {

  //默认或无参数的构造函数
  Animal() {
    System.out.println("I am an animal");
  }

  //参数化构造函数
  Animal(String type) {
    System.out.println("Type: "+type);
  }
}

class Dog extends Animal {

  //默认构造函数
  Dog() {

    //调用超类的参数化构造函数
    super("Animal");

    System.out.println("I am a dog");
  }
}

class Main {
  public static void main(String[] args) {
    Dog dog1 = new Dog();
  }
}
输出结果
Type: AnimalI am a dog

编译器可以自动调用无参数构造函数。但是,它不能调用带有参数的构造函数。

如果必须调用参数化的构造函数,则需要在子类构造函数中显式定义它,如上面代码中的语句:

super("Animal");

请注意,在上面的示例中,我们使用了super("Animal"),显式地调用参数化构造函数。在这种情况下,编译器不会调用超类的默认构造函数。


Java final 关键字

在本教程中,我们将通过示例学习Java final变量,final方法和final类。

在Java中,final关键字用于表示常量。它可以与变量,方法和类一起使用。

任何实体(变量,方法或类)一旦被声明final后,只能分配一次。也就是,

  • final变量不能用另一个值重新初始化

  • final方法不能被重写

  • final类不能被继承

1. Java final变量

在Java中,我们无法更改final变量的值。例如,

class Main {
  public static void main(String[] args) {

    //创建一个final变量
    final int AGE = 32;

    //尝试更改final变量
    AGE = 45;
    System.out.println("Age: " + AGE);
  }
}

在上述程序中,我们创建了一个名为age的final变量。并且我们尝试更改final变量的值。

运行程序时,将出现以下错误消息,提示编译错误。

cannot assign a value to final variable AGE
    AGE = 45;
    ^

注意:建议使用大写形式在Java中声明final变量。

2. Java final方法

在了解final方法和final类之前,请确保您了解Java继承

在Java中,该final方法不能被子类覆盖。例如,

class FinalDemo {
    //创建final方法
    public final void display() {
      System.out.println("这是Final方法。");
    }
}

class Main extends FinalDemo {
  //尝试重写final方法
  public final void display() {
    System.out.println("Final方法被覆盖。");
  }

  public static void main(String[] args) {
    Main obj = new Main();
    obj.display();
  }
}

在上面的示例中,我们创建了一个在FinalDemo类内部命名display()的final方法。在这里,Main类继承了FinalDemo类。

我们试图覆盖Main该类中的final方法。运行程序时,将出现以下错误消息,提示编译错误。

 display() in Main cannot override display() in FinalDemo
  public final void display() {
                    ^
  overridden method is final

3. Java final类

在Java中,final类不能被另一个类继承。例如,

final class FinalClass {
    //创建final方法
    public void display() {
      System.out.println("这是final方法。");
    }
}

class Main extends FinalClass {
  //尝试重写final方法
  public  void display() {
    System.out.println("重写final方法");
  }

  public static void main(String[] args) {
    Main obj = new Main();
    obj.display();
  }
}

在上面的示例中,我们创建了一个名为FinalClass的final类。在这里,我们试图通过Main类继承final类。

运行程序时,将出现以下错误消息,提示编译错误。

cannot inherit from final FinalClass
class Main extends FinalClass {
                   ^

Java instanceof 关键字

在本教程中,您将在示例的帮助下详细了解Java instanceof运算符。

在Java中,instanceof关键字是二进制运算符。它用于检查对象是否是特定类的实例。

运算符还检查对象是否是实现接口的类的实例(将在本教程后面讨论)。

instanceof的语法为:
result = objectName instanceof className;
 instanceof运算符的左操作数是对象名,右操作数是类名。如果对象是类的实例,则结果为true;如果不是,则为false。
class Main {
    public static void main (String[] args) {
        String name = "nhooo";
        Integer age = 22;

        System.out.println("name是String的实例吗: "+ (name instanceof String));
        System.out.println("age是Integer的实例吗: "+ (age instanceof Integer));
    }
}
输出:
name是String的实例吗: trueage是Integer的实例吗: true

在上面的示例中,我们创建了一个String类型的对象name和另一个Integer类型的对象age。 然后,我们使用instanceof运算符检查名称是否为String类型以及age是否为Integer类型。

instanceof 和 继承

在继承的情况下,instanceof运算符用于检查子类的对象是否也是超类的实例。

继承中的instanceof

class Animal {
}

//Dog类是Animal的子类
class Dog extends Animal {
}

class Main {
    public static void main(String[] args){
        Dog d1 = new Dog();

        //检查d1是否是Dog的对象
        System.out.println("d1是Dog的实例吗: "+ (d1 instanceof Dog));
       
        //检查d1是否是Animal的对象
        System.out.println("d1是Animal的实例吗: "+ (d1 instanceof Animal));
    }
}
输出:
d1是Dog的实例吗: true
d1是Animal的实例吗: true

在上面的示例中,d1是Dog和Animal类的实例。 因此,d1 instanceof Dog 和 d1 instanceof Animal 都为true。

Object类

在Java中,所有类都继承自Object类,继承Object类不使用extends关键字。这种继承在Java中隐式发生。

class Animal {
}

class Dog {
}

class Cat {
}
class Main {
    public static void main(String[] args) {
        Dog d1 = new Dog();
        Animal a1 = new Animal();
        Cat c1 = new Cat();

        System.out.println("d1是Object类的实例吗: "+ (d1 instanceof Object));
        System.out.println("a1是Object类的实例吗: "+ (a1 instanceof Object));   
        System.out.println("c1是Object类的实例吗: "+ (c1 instanceof Object));
    }
}
输出:
d1是Object类的实例吗: truea1是Object类的实例吗: truec1是Object类的实例吗: true

在上面的实例中,我们分别创建了类Animal、Dog和Cat的对象a1、d1和c1。我们已经使用instanceof操作符来检查这些对象a1, d1, c1是否也是Object类的对象。输出结果都为true(真)。

这是因为Object类是java.lang包中定义的根类。 所有其他类都是在Java中形成层次结构的Object类的子类。

对象向上转型和向下转型

在Java中,子类的对象可以视为父类的对象。这称为向上转型。通俗地讲即是将子类对象转为父类对象。此处父类对象可以是接口。

Java编译器自动执行向上转型。

对象向上转型

class Animal {
    public void displayInfo() {
        System.out.println("I am an animal.");
    }
}

class Dog extends Animal {
}

class Main {
    public static void main(String[] args) {
        Dog d1 = new Dog();
        Animal a1 = d1;
        a1.displayInfo();
    }
}
输出:
I am an animal.

在上面的实例中,我们创建了Dog类的对象d1。我们使用d1对象来创建Animal类的对象a1。这在Java中称为向上转型。

该代码执行没有任何问题。这是因为向上转型是由Java编译器自动完成的。

向下转型是向上转型的相反过程,也就是与向上转型相反,即是把父类对象转为子类对象。

在向下转型的情况下,父类的对象被视为子类的对象。我们必须在Java中显式地指示编译器向下转型。

对象向下转型的问题

class Animal {
}

class Dog extends Animal {
   public void displayInfo() {
       System.out.println("I am a dog.");
   }
}

class Main {
   public static void main(String[] args) {
       Animal a1 = new Animal();
       Dog d1 = (Dog)a1; // 向下类型转换
 
       d1.displayInfo();
   }
}

运行程序时,将获得名为ClassCastException的异常。让我们看看这里发生了什么。

在这里,我们创建了一个父类Animal的对象a1。然后我们尝试将a1对象强制转换为子类Dog的对象d1。

这引起了问题。 这是因为父类Animal的a1对象也可能引用其他子类。 如果我们与Dog一起创建了另一个子类Cat; 动物可能是猫,也可能是狗,引起歧义。

为了解决这个问题,我们可以使用instanceof运算符。这是如何做:

使用instanceof解决向下类型转换

class Animal {
}

class Dog extends Animal {
  public void displayInfo() {
     System.out.println("I am a dog");
  }
}

class Main {
  public static void main(String[] args) {
    Dog d1 = new Dog();
    Animal a1 = d1;    // 向上转型

    if (a1 instanceof Dog){
       Dog d2 = (Dog)a1;    // 向下转型
       d2.displayInfo();
    }
  }
}
输出:
I am a dog

在上面的示例中,我们使用instanceof运算符检查a1对象是否为Dog类的实例。仅当表达式a1 instanceof Dog为true时才进行向下类型转换。

接口 和 instanceof

instanceof运算符还用于检查类的对象是否也是实现该类的接口的实例。

接口中的instanceof

interface Animal {
}

class Dog implements Animal {
}

class Main {
   public static void main(String[] args) {
      Dog d1 = new Dog();
      System.out.println("d1对象是Animal的实例吗: "+(d1 instanceof Animal));
   }
}
输出:
d1对象是Animal的实例吗: true

在上面的示例中,我们创建了一个Dog实现Animal接口的类。

然后,创建Dog类的对象d1。我们已经使用instanceof运算符来检查d1对象是否也是Animal接口的实例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值