이 글은 주로 Java 오버로딩, 재작성, 생성자 함수에 대한 관련 정보를 소개합니다. 이 글을 통해 누구나 Java 객체지향 방법을 이해하고 익힐 수 있기를 바랍니다. 오버로딩 및 재작성 작성 및 생성자 예제에 대한 자세한 설명
메서드 오버라이딩
1. 오버라이딩은 상속 관계에서만 나타날 수 있습니다. 클래스가 상위 클래스로부터 메서드를 상속하면 상위 클래스의 메서드를 재정의할 수 있는 기회가 있습니다. 특별한 경우는 부모 클래스 메서드가 최종으로 표시되는 경우입니다. 재정의의 주요 장점은 하위 유형에 특정한 동작을 정의할 수 있다는 것입니다.
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. 상위 클래스에서 상속된 추상 메서드의 경우 하위 클래스에서 재정의하여 메서드를 설계하거나 하위 클래스를 추상으로 표시합니다. 따라서 추상 메서드는 다시 작성해야 하는 메서드라고 할 수 있습니다.
오버라이딩 메서드는 다형성을 달성하고 상위 클래스의 참조를 사용하여 하위 클래스 개체를 조작할 수 있지만 실제 작업에서는 개체가 고유한 메서드를 실행합니다.
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(){ } }
한 가지 원칙은 다음과 같습니다. 어떤 참조가 사용되든 컴파일러는 참조 클래스가 소유한 메서드만 호출합니다. 위의 예에서 h.buck()과 같은 하위 클래스별 메서드를 호출하면 컴파일러에서 오류가 발생합니다. 즉, 컴파일러는 객체 유형이 아닌 참조 유형만 살펴봅니다.
오버로딩 대신 적격 재작성 방법을 구현하려면 다음 요구 사항을 동시에 충족해야 합니다!
(그러나 더 광범위할 수 있습니다. 예를 들어 상위 클래스 메서드에는 패키지 액세스 권한이 있고 하위 클래스의 재정의된 메서드에는 공개 액세스 권한이 있습니다.)
예: Object 클래스에는 toString() 메서드가 있습니다. public 수정자는 잊어버리세요. 물론 컴파일러는 우리에게 교훈을 줄 수 있는 기회를 놓치지 않을 것입니다. 오류가 발생하는 이유는 액세스 한정자가 없는 메서드에는 패키지 액세스 권한이 있기 때문입니다. 물론 패키지 액세스 권한은 공개보다 엄격하므로 컴파일러는 오류를 보고합니다.
B. 재작성 규칙 2: 매개변수 목록은 재정의된 메서드의 목록과 동일해야 합니다.
Rewrite에는 나중에 등장할 Overload라는 쌍둥이 형제가 있습니다. 하위 클래스 메서드의 매개변수가 상위 클래스의 해당 메서드와 다른 경우에는 사람을 잘못 찾은 것입니다. 이는 재정의가 아니라 오버로드입니다.
상위 클래스 메서드 A: void eat(){} 하위 클래스 메서드 B: int eat(){} 매개변수는 동일하지만 반환 유형이 다르기 때문에 재정의되지 않습니다. **
상위 클래스 메소드 A: int eat(){} 하위 클래스 메소드 B: long eat(){} 반환 유형은 상위 클래스와 호환되지만 차이점이 있으므로 재정의는 아닙니다.
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(); } }
이 예에서 상위 클래스는 확인된 예외 Exception을 발생시키고, 하위 클래스에서 발생한 IOException은 Exception의 하위 클래스이므로 재정의된 메서드보다 더 제한된 예외를 발생시키는 것은 괜찮습니다. 반면에 상위 클래스가 IOException을 발생시키고 하위 클래스가 더 광범위한 예외를 발생시키는 경우 컴파일되지 않습니다.
참고: 이 제한은 확인된 예외에만 적용됩니다. RuntimeException 및 해당 하위 클래스에는 더 이상 이 제한이 적용되지 않습니다.
E. 재작성 규칙 5: final로 표시된 메서드는 재정의될 수 없습니다.
대표적인 것은 부모 클래스의 private 메소드입니다. 다음 예에서는 흥미로운 현상을 보여줍니다.
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."); } }
이 코드는 컴파일이 가능합니다. 표면적으로는 규칙 6을 위반한 것처럼 보이지만 실제로는 약간의 우연이었습니다. Animal 클래스의 eat() 메소드는 상속될 수 없으므로 Horse 클래스의 eat() 메소드는 재작성이나 오버로드가 아닌 완전히 새로운 메소드로서 Horse 클래스에만 속합니다! 이것은 많은 사람들을 혼란스럽게 하지만 이해하는 것은 그리 어렵지 않습니다.
main() 메소드가 다음과 같은 경우:
Animal h = new Horse(); //Horse h = new Horse(); h.eat();
컴파일러가 오류를 보고합니다. 이유는 무엇입니까? Horse 클래스의 eat() 메소드가 공개되었습니다! 호출이 가능해야 합니다! 다형성은 하위 클래스 객체의 메서드가 아닌 상위 클래스에서 참조하는 메서드만 살펴본다는 점을 명심하세요!
메서드 오버로딩
오버로딩은 친숙합니다. 메서드를 호출하기 전에 데이터 유형을 변환할 필요가 없으며 자동으로 일치하는 메서드를 찾습니다. 메서드 오버로드는 컴파일 타임에 호출할 메서드를 결정하는데, 이는 재정의와 다릅니다. 가장 일반적으로 사용되는 위치는 생성자의 오버로딩입니다.
/** * 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); } }
출력 결과:
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课程说,静态方法只能控制静态变量(他们本身没有),静态方法不能被重载和覆盖……”
大家不要相信啊!可以重载的。而且静态与非静态方法可以重载。
从重载的机制很容易就理解了,重载是在编译时刻就决定的了,非静态方法都可以,静态方法怎么可能不会呢?
위 내용은 Java 오버로딩, 재작성 및 생성자에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!