Maison  >  Article  >  Java  >  Explication détaillée de la surcharge, de la réécriture et des constructeurs Java

Explication détaillée de la surcharge, de la réécriture et des constructeurs Java

巴扎黑
巴扎黑original
2017-09-21 11:58:181902parcourir

Cet article présente principalement des informations pertinentes sur la surcharge, la réécriture et des exemples détaillés de fonctions de constructeur. J'espère qu'à travers cet article, tout le monde pourra comprendre et maîtriser la méthode orientée objet Java. Les amis dans le besoin pourront s'y référer

Exemples détaillés de surcharge, de réécriture et de constructeurs Java

La réécriture de méthodes

1. dans le cadre de la relation successorale. Lorsqu'une classe hérite d'une méthode de sa classe parent, elle a la possibilité de remplacer la méthode de la classe parent. Un cas particulier est celui où une méthode de classe parent est marquée comme finale. Le principal avantage de la substitution est la possibilité de définir un comportement spécifique à un sous-type.


class Animal {
    public void eat(){
      System.out.println ("Animal is eating.");
    }
  }

  class Horse extends Animal{
    public void eat(){
      System.out.println ("Horse is eating.");
    }
  }
2. Pour les méthodes abstraites héritées de la classe parent, concevez la méthode en la remplaçant dans la sous-classe ou marquez la sous-classe comme abstraite. Les méthodes abstraites peuvent donc être considérées comme des méthodes qui doivent être réécrites.

3. Le sens de la réécriture.


Les méthodes de remplacement peuvent réaliser un polymorphisme et utiliser des références de la classe parent pour manipuler des objets de sous-classe, mais en fonctionnement réel, l'objet exécutera sa propre méthode unique.


public class Test {
    public static void main (String[] args) {
      Animal h = new Horse();
      h.eat(); 
    }
  }

  class Animal {
    public void eat(){
      System.out.println ("Animal is eating.");
    }
  }

  class Horse extends Animal{
    public void eat(){
      System.out.println ("Horse is eating.");
    }
    public void buck(){
    }
  }
Un principe est le suivant : quelle que soit la référence utilisée, le compilateur n'appellera que les méthodes appartenant à la classe de référence. Si vous appelez une méthode spécifique à une sous-classe, telle que h.buck() dans l'exemple ci-dessus ; le compilateur se plaindra (erreur de compilation). Autrement dit, le compilateur examine uniquement les types de référence, pas les types d'objets.

4. Règles de réécriture des méthodes.


Si vous souhaitez implémenter une méthode de substitution qualifiée au lieu de surcharger, vous devez répondre aux exigences suivantes en même temps !

A. Une des règles de réécriture : la méthode surchargée ne peut pas avoir un niveau d'accès plus restrictif que la méthode surchargée.

(Mais cela peut être plus large. Par exemple, la méthode de la classe parent a des droits d'accès au package et la méthode remplacée de la sous-classe a des droits d'accès publics.)
Par exemple : la classe Object a une méthode toString() , commencez à surcharger cette méthode. Parfois, nous avons tendance à oublier le modificateur public, et le compilateur ne manquera certainement aucune occasion de nous donner une leçon. La raison de l'erreur est la suivante : les méthodes sans aucun modificateur d'accès ont des droits d'accès aux packages. Les droits d'accès aux packages sont bien sûr plus stricts que ceux publics, le compilateur signalera donc une erreur.

B. Règle de réécriture 2 : La liste des paramètres doit être la même que celle de la méthode remplacée.


Rewrite a un frère jumeau appelé Overload, qui apparaîtra plus tard. Si les paramètres de la méthode de sous-classe sont différents de la méthode correspondante de la classe parent, alors vous vous êtes trompé de personne. Il s'agit d'une surcharge et non d'une substitution.

C. Troisième règle de réécriture : **Le type de retour doit être le même que le type de retour de la méthode remplacée.


Méthode de classe parent A : void eat(){} Méthode de sous-classe B : int eat(){} Bien que les paramètres soient les mêmes, les types de retour sont différents, ils ne sont donc pas réécrits. **


Méthode de classe parent A : int eat(){} Méthode de sous-classe B : long eat(){} Bien que le type de retour soit compatible avec la classe parent, la différence est différente, donc il n'est pas un remplacement.

D. Règle de réécriture 4 : La méthode de substitution ne peut pas lever de nouvelles exceptions ou d'exceptions vérifiées qui sont plus larges que les exceptions vérifiées déclarées par la méthode de substitution. Mais il est possible de lancer des exceptions moins nombreuses, plus limitées ou inexistantes.


import java.io.*;
  /**
  * Java学习交流QQ群:589809992 我们一起学Java!
  */
  public class Test {
    public static void main (String[] args) {
      Animal h = new Horse();
      try {
        h.eat(); 
      }
      catch (Exception e) {
      }
    }
  }

  class Animal {
    public void eat() throws Exception{
      System.out.println ("Animal is eating.");
      throw new Exception();
    }
  }

  class Horse extends Animal{
    public void eat() throws IOException{
      System.out.println ("Horse is eating.");
      throw new IOException();
    }
  }
Dans cet exemple, la classe parent lève une exception d'exception vérifiée, et l'exception IOException lancée par la sous-classe est une sous-classe d'exception, c'est-à-dire qu'elle est réécrite La méthode . renvoie une exception plus limitée, ce qui est OK. Si, en revanche, la classe parent lève IOException et que la sous-classe lève une exception plus large, elle ne sera pas compilée.

Remarque : Cette restriction concerne uniquement les exceptions vérifiées. Exception d'exécution RuntimeException et ses sous-classes ne sont plus soumises à cette restriction.

E. Règle de réécriture 5 : Les méthodes marquées comme finales ne peuvent pas être remplacées.

F. Règle de réécriture 6 : Si une méthode ne peut pas être héritée, elle ne peut pas être remplacée.


La méthode typique est la méthode privée de la classe parent. L'exemple suivant produit un phénomène intéressant.


public class Test {
    public static void main (String[] args) {
      //Animal h = new Horse();
      Horse h = new Horse();
      h.eat();
    }
  }

  class Animal {
    private void eat(){
      System.out.println ("Animal is eating.");
    }
  }

  class Horse extends Animal{
    public void eat(){
      System.out.println ("Horse is eating.");
    }
  }
Ce code peut être compilé. En apparence, cela ressemble à une violation de la règle 6, mais en réalité, il s’agit plutôt d’une coïncidence. La méthode eat() de la classe Animal ne peut pas être héritée, donc la méthode eat() de la classe Horse est une toute nouvelle méthode, pas une réécriture ou une surcharge, mais une toute nouvelle méthode qui n'appartient qu'à la classe Horse ! Cela déroute beaucoup de gens, mais ce n’est pas si difficile à comprendre.


Si la méthode main() est comme ceci :


  Animal h = new Horse();
  //Horse h = new Horse();
  h.eat();
Le compilateur signalera une erreur, pourquoi ? La méthode eat() de la classe Horse est publique ! Il devrait pouvoir être appelé ! Veuillez garder à l’esprit que le polymorphisme ne s’intéresse qu’aux méthodes référencées par la classe parent, pas aux méthodes de l’objet sous-classe !

Surcharge des méthodes

La surcharge est conviviale, elle ne nécessite pas de convertir le type de données avant d'appeler une méthode, elle trouvera automatiquement une méthode de correspondance . La surcharge de méthode détermine la méthode à appeler au moment de la compilation, ce qui est différent de la substitution. L'endroit le plus couramment utilisé est la surcharge du constructeur.

1. Surcharge des paramètres de type de données de base.


/**
 * Java学习交流QQ群:589809992 我们一起学Java!
 */
public class Test {
    static void method(byte b){
      System.out.println ("method:byte");
    }
    static void method(short s){
      System.out.println ("method:short");
    }
    static void method(int i){
      System.out.println ("method:int");
    }
    static void method(float f){
      System.out.println ("method:float");
    }
    static void method(double d){
      System.out.println ("method:double");
    }
    public static void main (String[] args) {
      method((byte)1);
      method('c');
      method(1);
      method(1L);
      method(1.1);
      method(1.1f);
    }
  }
Résultat de sortie :


method:byte
method:int
method:int
method:float
method:double
method:float

  可以看出:首先要寻找的是数据类型正好匹配方法。如果找不到,那么就提升为表达能力更强的数据类型,如上例没有正好容纳long的整数类型,那么就转换为 float类型的。如果通过提升也不能找到合适的兼容类型,那么编译器就会报错。反正是不会自动转换为较小的数据类型的,必须自己强制转换,自己来承担转变后果。

  char类型比较特殊,如果找不到正好匹配的类型,它会转化为int而不是short,虽然char是16位的。

2、重载方法的规则。

A、被重载的方法必须改变参数列表。

参数必须不同,这是最重要的!不同有两个方面,参数的个数,参数的类型,参数的顺序。

B、被重载的方法与返回类型无关。

也就是说,不能通过返回类型来区分重载方法。

C、被重载的方法可以改变访问修饰符。

没有重写方法那样严格的限制。

D、被重载的方法可以声明新的或者更广的检查异常。

没有重写方法那样严格的限制。

E、方法能够在一个类中或者在一个子类中被重载。

3、带对象引用参数的方法重载。


class Animal {}
  class Horse extends Animal{}

  public class Test {
    static void method(Animal a){
      System.out.println ("Animal is called.");
    }
    static void method(Horse h){
      System.out.println ("Horse is called.");
    }
    public static void main (String[] args) {
      Animal a = new Animal();
      Horse h = new Horse();
      Animal ah = new Horse();

      method(a);
      method(h);
      method(ah);
    }
  }

输出结果是:


Animal is called.
Horse is called.
Animal is called.

前两个输出没有任何问题。第三个方法为什么不是输出“Horse is called.”呢?还是那句老话,要看引用类型而不是对象类型,方法重载是在编译时刻就决定的了,引用类型决定了调用哪个版本的重载方法。

4、重载和重写方法区别的小结。

如果能彻底弄明白下面的例子,说明你对重载和重写非常了解了,可以结束这节的复习了。


class Animal {
    public void eat(){
      System.out.println ("Animal is eating."); 
    }
  }
  class Horse extends Animal{
    public void eat(){
      System.out.println ("Horse is eating."); 
    }
    public void eat(String food){
      System.out.println ("Horse is eating " + food);
    }
  }

  public class Test {
    public static void main (String[] args) {
      Animal a = new Animal();
      Horse h = new Horse();
      Animal ah = new Horse();

      a.eat();
      h.eat();
      h.eat("apple");
      ah.eat();
      //a.eat("apple");
      //ah.eat("apple");
    }
  }

四个输出分别是什么?被注释的两条语句为什么不能通过编译?

第一条:a.eat(); 普通的方法调用,没有多态,没什么技术含量。调用了Animal类的eat()方法,输出:Animal is eating.
第二条:h.eat(); 普通的方法调用,也没什么技术含量。调用了Horse类的eat()方法,输出:Horse is eating.
第三条:h.eat(“apple”); 重载。Horse类的两个eat()方法重载。调用了Horse类的eat(String food)方法,输出:Horse is eating apple
第四条:ah.eat(); 多态。前面有例子了,不难理解。输出:Horse is eating.
第五条:a.eat(“apple”); 低级的错误,Animal类中没有eat(String food)方法。因此不能通过编译。
第六条:ah.eat(“apple”); 关键点就在这里。解决的方法还是那句老话,不能看对象类型,要看引用类型。Animal类中没有eat(String food)方法。因此不能通过编译。

小结一下:多态不决定调用哪个重载版本;多态只有在决定哪个重写版本时才起作用。

重载对应编译时,重写对应运行时。够简洁的了吧!

构造方法

构造方法是一种特殊的方法,没有构造方法就不能创建一个新对象。实际上,不仅要调用对象实际类型的构造方法,还要调用其父类的构造方法,向上追溯,直到 Object类。构造方法不必显式地调用,当使用new关键字时,相应的构造方法会自动被调用。

1、构造方法的规则。

A、构造方法能使用任何访问修饰符。包括private,事实上java类库有很多都是这样的,设计者不希望使用者创建该类的对象。

B、构造方法的名称必须与类名相同。这样使得构造方法与众不同,如果我们遵守sun的编码规范,似乎只有构造方法的首字母是大写的。

C、构造方法不能有返回类型。

反过来说,有返回类型的不是构造方法


  public class Test {
    int Test(){
      return 1;
    }
  }

这个方法是什么东西?一个冒充李逵的李鬼而已,int Test()和其他任何普通方法没什么两样,就是普通的方法!只不过看起来很恶心,类似恶心的东西在考试卷子里比较多。

D、如果不在类中创建自己的构造方法,编译器会自动生成默认的不带参数的构造函数。

这点很容易验证!写一个这样简单的类,编译。


class Test {
}

对生成的Test.class文件反编译:javap Test,可以看到:


D:"JavaCode"bin>javap Test
Compiled from "Test.java"
class Test extends java.lang.Object{
  Test();
}

看到编译器自动添加的默认构造函数了吧!

E、如果只创建了带参数的构造方法,那么编译器不会自动添加无参的构造方法的!

F、在每个构造方法中,如果使用了重载构造函数this()方法,或者父类的构造方法super()方法,那么this()方法或者super()方法必须放在第一行。而且这两个方法只能选择一个,因此它们之间没有顺序问题。

G、除了编译器生成的构造方法,而且没有显式地调用super()方法,那么编译器会插入一个super()无参调用。

H、抽象类有构造方法。

静态方法的重载与重写(覆盖)

1、静态方法是不能被覆盖的。可以分两种情况讨论:

A、子类的非静态方法“覆盖”父类的静态方法。

这种情况下,是不能通过编译的。


class Father{
   static void print(){
     System.out.println ( " in father  method " );
   }
}
class Child extends Father{
   void print(){
     System.out.println ( " in child method " );
   }
}

static方法表示该方法不关联具体的类的对象,可以通过类名直接调用,也就是编译的前期就绑定了,不存在后期动态绑定,也就是不能实现多态。子类的非静态方法是与具体的对象绑定的,两者有着不同的含义。

B、子类的静态方法“覆盖”父类静态方法。

这个覆盖依然是带引号的。事实上把上面那个例子Child类的print方法前面加上static修饰符,确实能通过编译!但是不要以为这就是多态!多态的特点是动态绑定,看下面的例子:


class Father{
   static void print(){
     System.out.println ( " in father  method " );
   }
}
class Child extends Father{
   static void print(){
     System.out.println ( " in child method " );
   }
}

class Test{
   public static void main (String[] args) {
     Father f = new Child();
     f.print();
   }
}

输出结果是:in father method

从这个结果可以看出,并没有实现多态。

但是这种形式很迷惑人,貌似多态,实际编程中千万不要这样搞,会把大家搞懵的!

它不符合覆盖表现出来的特性,不应该算是覆盖!

总而言之,静态方法不能被覆盖。

2、静态方法可以和非静态方法一样被重载。

这样的例子太多了,我不想写例程了。看看java类库中很多这样的例子。

如java.util.Arrays类的一堆重载的binarySearch方法。

在这里提一下是因为查资料时看到这样的话“sun的SL275课程说,静态方法只能控制静态变量(他们本身没有),静态方法不能被重载和覆盖……”

大家不要相信啊!可以重载的。而且静态与非静态方法可以重载。

从重载的机制很容易就理解了,重载是在编译时刻就决定的了,非静态方法都可以,静态方法怎么可能不会呢?

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn