Made by Mike_Zhang
Java主题:
OOP Introduction 请阅读本博客的Java面向对象编程详细介绍 - Java OOP Detailed Introduction
1. Review
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Triangle { void Print () { System.out.println("A Triangle printed!" ); } }class RightTriangle extends Triangle { void Print () { System.out.println("A Right Triangle printed!" ); } }class IsoscelesRightTriangle extends Triangle { void Print () { System.out.println("A Isosceles Right Triangle printed!" ); } }class EquilateralTriangle extends Triangle { void Print () { System.out.println("A Isosceles Equilateral Triangle printed!" ); } }public class PrintTriangle { public static void PrintOut (Triangle x) { x.Print(); } public static void main (String[] args) { Triangle aNewRightTriangle = new RightTriangle (); PrintOut(aNewRightTriangle);; Triangle aNewIsoscelesRightTriangle = new IsoscelesRightTriangle (); PrintOut(aNewIsoscelesRightTriangle); Triangle aNewEquilateralTriangle = new EquilateralTriangle (); PrintOut(aNewEquilateralTriangle); } }
以上案例是在在之前文章Java面向对象编程(OOP)的继承性(Inheritance) 与Java面向对象编程(OOP)的多态性(Polymorphism) 中提到了,运用了OOP的两大特性。
看以下例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Shape { public void name () { System.out.println("Shape" ); } public void print () { System.out.println("A Shape printed." ); } }class Rectangle extends Shape { public void name () { System.out.println("Rectangle" ); } public void print () { System.out.println("A Rectangle printed." ); } }class Circle extends Shape { public void name () { System.out.println("Circle" ); } public void print () { System.out.println("A Circle printed." ); } }class Triangle extends Shape { public void name () { System.out.println("Triangle" ); } public void print () { System.out.println("A Triangle printed." ); } }public class PrintAbsShape { static void PrintOut (Shape s) { s.print(); } public static void main (String[] args) { PrintOut(new Rectangle ()); PrintOut(new Triangle ()); PrintOut(new Circle ()); } }
但是仔细观察发现,第一个例子中的父类Triangle
与第二个例子中的父类Shape
中的方法始终没有被引用 ,显得十分多余。因为此Shape
父类本来就是提供一个入口 ,并让其子类继承并对其方法进行重写等操作。 因此父类中的方法不需要被定义的十分具象,只需要告诉衍生类方法的大概模样就足够了,因此父类可以变的 抽象(abstract) 或者只成为一个连通子类的 接口(interface) 。
2. Abstract class & method 在Java中,通过修饰符abstract
来修饰一个方法为抽象的 。此修饰的方法是残缺的,不完整的 ,只包含方法的声明并没有方法体 ,语法:
把abstract
应用到上面的例子中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 abstract class Shape { public abstract void name () ; public abstract void print () ; }class Rectangle extends Shape { public void name () { System.out.println("Rectangle" ); } public void print () {} }class Circle extends Shape { public void name () { System.out.println("Circle" ); } public void print () {} }class Triangle extends Shape { public void name () { System.out.println("Triangle" ); } public void print () {} }class RightTriangle extends Triangle { public void name () { System.out.println("Right Triangle" ); } public void print () { System.out.println("A Right Triangle printed" ); } }class IsoscelesTriangle extends Triangle { public void name () { System.out.println("Isosceles Triangle" ); } public void print () { System.out.println("A Isosceles Triangle printed" ); } }class EquilateralTriangle extends Triangle { public void name () { System.out.println("Equilateral Triangle" ); } public void print () { System.out.println("A Equilateral Triangle printed" ); } }public class PrintAbsShape { static void PrintOut (Shape s) { s.print(); } public static void main (String[] args) { PrintOut(new RightTriangle ()); PrintOut(new IsoscelesTriangle ()); PrintOut(new EquilateralTriangle ()); } }
包含abstract
方法的类被称为abstract
类,此类必须被修饰为abstract
。abstract
类允许在其类中创建0个,一个或者多个 abstract
方法,
abstract
类中的所有abstract
方法需要被其子类重写 以完成方法体。当某一子类继承abstract
父类后,如要使用此子类创造对象,必须完成子类中所有从父类abstract
方法继承来方法的定义。static
方法、private
实例方法、构造方法不能被重写
若不对父类中所有的abstract
方法进行重写 ,则此子类也包含了从父类继承的abstract
方法,则其也是abstract
类 ,也需要修饰为abstract
。
不允许直接用abstract类来创建实例 ,只可以来定义类型。以下不允许:
1 Shape aShape = new Shape ();
子类可以重写父类中的非abstract
方法 并定义为abstract
,可以使此父类中的方法在子类中失效。
一个abstract
类的父类可能是非abstract
类。
3. Interface
interface
把abstract
类更进一步的抽象,使这个类变得完全abstract
。
在此类中只声明方法名 、参数列表 和返回值类型 ,没有方法体 ,只提供方法的形式 ,没有定义方法体 。
对于实例成员(Instance members):
不可以定义属性;
都被public
和abstract
修饰(隐性修饰,关键字可省略);
对于静态成员(Static members):
所有属性都被public
,static
和final
修饰(隐性修饰,关键字可省略);
所有方法都被public
(隐性修饰,关键字可省略),并为非abstract
类。
任何 实现(implement) 此接口的类都会与interface
类相似,都会得知能从此接口调用到什么方法,类似于类之间建立了一个协议。
使用interface
时,用interface
关键字 代替原来的class
关键字。
interface
应用到上面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 interface Shape { int NUM = 10 ; void name () ; void print () ; static String getShapeSuperName () {return "Shape" } }class Rectangle implements Shape { public void name () { System.out.println("Rectangle" ); } public void print () {} }class Circle implements Shape { public void name () { System.out.println("Circle" ); } public void print () {} }class Triangle implements Shape { public void name () { System.out.println("Triangle" ); } public void print () {} }class RightTriangle extends Triangle { public void print () { System.out.println("A Right Triangle printed" ); } }class IsoscelesTriangle extends Triangle { public void print () { System.out.println("A Isosceles Triangle printed" ); } }class EquilateralTriangle extends Triangle { public void print () { System.out.println("A Equilateral Triangle printed" ); } }public class PrintShape { static void PrintOut (Shape s) { s.print(); } public static void main (String[] args) { PrintOut(new RightTriangle ()); PrintOut(new IsoscelesTriangle ()); PrintOut(new EquilateralTriangle ()); } }
当某一类要使用此接口时,也就是说此类要实现(implements)此接口,就需要使用implements
关键字,类似于继承。
此类可以访问父类中constants 和 static
的方法;
此类通过重写来具化声明在接口中的方法;
如果没有初始化父类中所有的abstract
方法,则此方法需要被定义为abstract
。
当此类实现接口后,其就变成了一个常规的类,能够被子类继承,如 RightTriangle
继承 Triangle
等。
interface
中的方法是被隐性修饰为public
的。当某类实现此interface
时,此类中从interface
重写的方法必须被修饰为public
,否则会变为默认访问权限,会导致此类被继承后的访问权限变小,产生错误。
上面例子main()中发生了upcasting,但是并不用明确到底转型到了哪个Shape
,无论是正常的Shape
,abstract
修饰的Shape
还是Shape
接口。
以上例子中,还有一个定义在interface
中的属性int NUM = 10;
,此属性都是隐性修饰为static
和final
4. Interface inheritance 4.1 Combined interface
在继承中,子类每次只能继承一个 父类,因为父类是一个完整的类,有具体的内存空间联系,同时继承多个类会导致冲突,如下:
1 2 3 public class ClassA {...}public class ClassB {...}public class ClassAandB extends ClassA , ClassB{...}
但是一个interface
只是一个形式,没有具体的内存空间与之联系,因此interface
与继承不同,一个类可以同时实现多个 interface
,只需要在implements
关键字后列出所有interface
名并用逗号隔开。语法:
1 class ClassA implements InterfaceA , InterfaceB, InterfaceC{}
应用到上面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 interface Shape { int NUM = 10 ; void name () ; void print () ; }interface Perimeter { void getPerimeter () ; }interface Area { void getArea () ; }class GreatShape implements Shape , Perimeter, Area{ public void name () {System.out.println("Great Shape with Perimeter and Area" );} public void print () {System.out.println("A Great Shape printed" );} public void getPerimeter () {System.out.println("Print the Perimeter of A Great Shape" );} public void getArea () {System.out.println("Print the Area of A Great Shape" );} }class GreatTriangle extends GreatShape { public void name () {System.out.println("Great Triangle with Perimeter and Area" );} public void print () {System.out.println("A Great Triangle printed" );} public void getPerimeter () {System.out.println("Print the Perimeter of A Great Triangle" );} public void getArea () {System.out.println("Print the Area of A Great Triangle" );} }public class PrintShape { static void PrintOut (Shape s) { s.print(); } static void PrintName (Shape x) { x.name(); } static void PrintPerimeter (Perimeter x) { x.getPerimeter(); } static void PrintArea (Area x) { x.getArea(); } public static void main (String[] args) { GreatTriangle g = new GreatTriangle (); PrintOut(g); PrintName(g); PrintPerimeter(g); PrintArea(g); } }
输出:
1 2 3 4 A Great Triangle printed Great Triangle with Perimeter and Area Print the Perimeter of A Great Triangle printed Print the Area of A Great Triangle printed
以上例子中,PrintShape
类中有4个方法,分别使用了不同接口当作其方法的参数,在其main()
中当一个GreatTriangle
对象创建并调用这4个方法时,此对象会upcast到这4个接口,并late binding到相应的方法体。
4.2 Inherited interface
当需要给某个interface
添加新的方法 ,或者要结合 多个interface
时,可以让某个interface
对另一个或多个interface
进行继承,这会产生新的 interface
。
应用到上面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 interface Shape { int NUM = 10 ; void name () ; void print () ; }interface Perimeter { void getPerimeter () ; }interface Area { void getArea () ; }interface GreatShapeInterface extends Shape , Perimeter, Area{ public void name () ; public void print () ; public void getPerimeter () ; public void getArea () ; public void what () ; }class GreatTriangle implements GreatShapeInterface { public void name () {System.out.println("Great Triangle with Perimeter and Area" );} public void print () {System.out.println("A Great Triangle printed" );} public void getPerimeter () {System.out.println("Print the Perimeter of A Great Triangle" );} public void getArea () {System.out.println("Print the Area of A Great Triangle" );} public void what () {System.out.println("Inherited form several interfaces!" );} }public class PrintShape { static void PrintOut (Shape s) { s.print(); } static void PrintName (Shape x) { x.name(); } static void PrintPerimeter (Perimeter x) { x.getPerimeter(); } static void PrintArea (Area x) { x.getArea(); } static void What (GreatShapeInterface x) { x.what(); } public static void main (String[] args) { GreatTriangle g = new GreatTriangle (); PrintOut(g); PrintName(g); PrintPerimeter(g); PrintArea(g); What(g); } }
注意: 当通过继承来结合一些接口时,要注意各个接口中的方法名是否相同,为增加可读性 以及减少错误 的产生,最好避免使用同名的方法 。
5. Interface field 定义在接口中的属性都是隐性修饰为static
和final
,并且是public
的。 注意定义在接口中的属性必须为编译时常量 (compile-time constant) 不能是空白常量 (blank final) 。 此类属性并不是接口的一部分,只是被储存在接口的静态 (static) 内存中。
6. Interface Cloning 6.1 Original copy 一般对一个基础变量进行复制会进行以下语句:
1 2 int int1 = 1 ; int1 = int2;
但对一个对象进行复制也进行类似语句:
1 2 classType object1 = new classType ();classType object2 = object1;
这样会使两个对象指向同一个引用,并不是单独的,改变一个对象的属性会影响到另外一个,例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Shape { private int number; public int getNumber () { return number; } public void setNumber (int inNum) { number = inNum; } }public class CopyObjectTest { public static void main (String[] args) { Shape s1 = new Shape (); s1.setNumber(111111 ); Shape s2 = s1; s1.setNumber(222222 ); System.out.printf("s1 number: %d\ns2 number: %d\n\n" ,s1.getNumber(),s2.getNumber()); System.out.printf("s1 == s2? %b" ,s1 == s2); } }
输出:
1 2 3 4 s1 number: 222222 s2 number: 222222 s1 == s2? true
6.2 Shallow Clone 为了解决上面所说的问题,就需要用到clone
方法,可以使复制的对象一开始有和被复制的对象有相同的成员,但之后也可以被单独对待 ,有自己的属性。
A shallow copy - C. S. Horstmann, Core Java. Boston: Pearson, 2019.
clone
方法是在Object
类中被修饰为protected
的方法。不能随意的调用。只能进行属性间的复制,也就是说只能对对象中为基础类型(primitive type)的属性进行复制。若对一个引用类型的属性或对象进行复制,则只会使克隆的对象指向相同的引用,和被克隆的对象有相同的信息。 因此clone分为Shallow Clone(浅克隆)和Deep Clone(深克隆) 。
Shallow Clone 只会克隆基础类型的属性,不会克隆引用类型的属性。
A shallow copy - C. S. Horstmann, Core Java. Boston: Pearson, 2019.
Shallow Clone步骤:
实现Cloneable
接口,否则在非Cloneable
对象调用clone()
方法会抛出CloneNotSupportedException
异常;
重写clone()
方法并修饰为public
,添加异常处理以处理CloneNotSupportedException
异常。
应用到前面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Shape implements Cloneable { private int number; public int getNumber () { return number; } public void setNumber (int inNum) { number = inNum; } public Object clone () { Shape temp = null ; try { temp = (Shape) super .clone(); } catch (CloneNotSupportedException e){ e.printStackTrace(); } return temp; } }public class ShallowCloneTest { public static void main (String[] args) { Shape s1 = new Shape (); s1.setNumber(11111 ); s1.setName("shape1&2" ); Shape s2 = (Shape)s1.clone(); System.out.printf("s1 number: %d, s2 number: %d\ns1 name: %s, s2 name: %s\n\n" ,s1.getNumber(),s2.getNumber(),s1.getName(),s2.getName()); s1.setNumber(22222 ); s1.setName("s1 Updated" ); System.out.printf("s1 number: %d, s2 number: %d\ns1 name: %s, s2 name: %s\n\n" ,s1.getNumber(),s2.getNumber(),s1.getName(),s2.getName()); System.out.printf("s1 == s2? %b" ,s1 == s2); } }
输出:
1 2 3 4 5 6 7 s1 number: 11111 , s2 number: 11111 s1 name: shape1&2 , s2 name: shape1&2 s1 number: 22222 , s2 number: 11111 s1 name: s1 Updated, s2 name: shape1&2 s1 == s2? false
接下来加入一个新的类Position
,并当作Shape类的一个属性,进行Shallow Clone:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 class Shape implements Cloneable { private int number; private String name; private Position pos; public int getNumber () { return number; } public void setNumber (int inNum) { number = inNum; } public String getName () { return name; } public void setName (String n) { name = n; } public String getPos () { return pos.getPosition(); } public void setPos (Position inPos) { pos = inPos; } public Object clone () { Shape temp = null ; try { temp = (Shape) super .clone(); } catch (CloneNotSupportedException e){ e.printStackTrace(); } return temp; } }class Position { private int x; private int y; public String getPosition () { return ("(" +x+"," +y+")" ); } public void setPosition (int InX, int Iny) { x = InX; y = Iny; } }public class DeepCloneTest { public static void main (String[] args) { Position aPos = new Position (); aPos.setPosition(1 ,1 ); Shape s1 = new Shape (); s1.setPos(aPos); Shape s2 = (Shape)s1.clone(); System.out.printf("s1 position: %s, s2 position: %s\n\n" ,s1.getPos(),s2.getPos()); aPos.setPosition(2 ,2 ); s1.setPos(aPos); System.out.printf("s1 position: %s, s2 position: %s\n\n" ,s1.getPos(),s2.getPos()); System.out.printf("s1 == s2? %b" ,s1 == s2); } }
输出:
1 2 3 4 5 s1 position: (1 ,1 ), s2 position: (1 ,1 ) s1 position: (2 ,2 ), s2 position: (2 ,2 ) s1 == s2? false
以上例子印证了Shallow Clone 只会克隆基础类型的属性,不会克隆引用类型的属性。
因此需要Deep Clone,不仅仅把reference type的属性克隆,也同时把reference type属性的引用地址克隆,达到彻底的克隆。
6.3 Deep Clone 为了实现Deep Clone,在以上例子中也需要把Position
类可克隆化,并且修改其clone()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 class Shape implements Cloneable { private int number; private String name; private Position pos; public int getNumber () { return number; } public void setNumber (int inNum) { number = inNum; } public String getName () { return name; } public void setName (String n) { name = n; } public String getPos () { return pos.getPosition(); } public void setPos (Position inPos) { pos = inPos; } public Object clone () { Shape temp = null ; try { temp = (Shape) super .clone(); temp.pos = (Position) pos.clone(); } catch (CloneNotSupportedException e){ e.printStackTrace(); } return temp; } }class Position implements Cloneable { private int x; private int y; public Object clone () { Position temp = null ; try { temp = (Position) super .clone(); } catch (CloneNotSupportedException e){ e.printStackTrace(); } return temp; } public String getPosition () { return ("(" +x+"," +y+")" ); } public void setPosition (int InX, int Iny) { x = InX; y = Iny; } }public class DeepCloneTest { public static void main (String[] args) { Position aPos = new Position (); aPos.setPosition(1 ,1 ); Shape s1 = new Shape (); s1.setPos(aPos); Shape s2 = (Shape)s1.clone(); System.out.printf("s1 position: %s, s2 position: %s\n\n" ,s1.getPos(),s2.getPos()); aPos.setPosition(2 ,2 ); s1.setPos(aPos); System.out.printf("s1 position: %s, s2 position: %s\n\n" ,s1.getPos(),s2.getPos()); System.out.printf("s1 == s2? %b" ,s1 == s2); } }
输出:
1 2 3 4 5 s1 position: (1 ,1 ), s2 position: (1 ,1 ) s1 position: (2 ,2 ), s2 position: (1 ,1 ) s1 == s2? false
在决定是否使用clone()
方法前,考虑:
默认的clone()
方法是否适合;
若不适合,则重写clone()
方法;
不应该使用clone()
方法。
若考虑使用:
实现Cloneable
接口;
重写clone()
方法,并用public
修饰。
End 两个使用interface
的原因:
使某一对象upcast至不止一个父类型,使其变得灵活;
防止这一抽象的类被直接使用
如果明确某一类会被定义为父类,则可以直接让其定义成一个interface
(或者abstract
,但是优先考虑interface
)。 但要注意不能过度使用interface
,可以先写出具象的父类,分析必要性之后再将其改成interface
。
参考 B. Eckel, Thinking in java . Upper Saddle River, N.Y. Prentice Hall, 2014. C. S. Horstmann, Core Java . Boston: Pearson, 2019.
写在最后 Java的接口还有更深层次的内容,会继续更新. 最后,希望大家一起交流,分享,指出问题,谢谢!
原创文章,转载请标明出处 Made by Mike_Zhang
感谢你的支持