Java变成笔记4:复用类

图源:Java Switch语句(用法详解)-java教程-PHP中文网

类是OOP编程中的代码组织单元,无论是OOP的类还是面向过程的函数,其目的都是为了实现代码复用

通过类实现代码复用的两大途径是:继承和组合。

组合

组合,简单地说就是将一个类地实例以属性地方式存在于另一个类中。

package ch4.compose;class Compose {}public class MyClass {private Compose compose;...
}

当然,这样地compose仅仅是一个引用,要让它有用必须进行初始化。和普通类型的属性一样,对组合的对象初始化有多种方式。

  1. 声明的同时初始化:
package ch4.compose2;class Compose {}public class MyClass {private Compose compose = new Compose();...
}

这样做的好处是,初始化行为是在所有构造函数调用之前,换句话说,即使存在多个重载的构造函数,compose属性也能得到有效的初始化。

  1. 在构造函数中初始化:
package ch4.compose3;class Compose {}public class MyClass {private Compose compose;public MyClass() {compose = new Compose();}...
}

这也是一种常见方式。

  1. 由外部传入:
package ch4.compose4;class Compose {}public class MyClass {private Compose compose;public MyClass(Compose compose) {this.compose = compose;}...
}

可以通过构造函数或者其它方法传参的方式传入一个compose的引用,在设计模式的相关实现中这种方式非常常见。

  1. 惰性初始化:
package ch4.compose5;class Compose {}public class MyClass {private Compose compose;public MyClass() {}public Compose getCompose() {if (compose == null) {compose = new Compose();}return compose;}...
}

这种初始化方式的优点在于可以将比较消耗性能的对象初始化工作延后到真正需要对象的时候,这也是单例模式常用的方式。但需要注意的是,在单进程下这样做并没有什么问题,如果在编写并发程序的时候,要实现这种效果就要付出更大的努力(比如使用资源锁)。

继承

Java的继承语法与其它主流语言类似,不过因为Java采用单根继承,所以所有没有指定继承的类,实际上都是隐式继承自Object,也就是说所有类都是Object类的子类。

package ch4.inheritence;class Parent{}public class Child extends Parent {public static void main(String[] args) {Child c = new Child();if (c instanceof Object){System.out.println("c is Object.");}if (c instanceof Parent){System.out.println("c is Parent.");}if (c instanceof Child){System.out.println("c is Child.");}// c is Object.// c is Parent.// c is Child.}
}

可以使用instanceof操作符判断某个对象是否为某个类的实例,很多语言中都有类似的用法。

通过上边的示例可以看到,通过继承,c这个实例可以具备多种“身份”,所以通常会说继承是一种"is"关系

super

如果存在继承关系,并且需要在当前类中使用父类的引用,可以使用super关键字,该关键字通常用于在子类中调用父类的方法:

package ch4.inheritence2;class Parent{public void display(){System.out.println("Parent is displayed.");}public void desdroy(){System.out.println("parent is desdroyed.");}
}public class Child extends Parent {@Overridepublic void display() {super.display();System.out.println("Child is desplayed.");}@Overridepublic void desdroy() {System.out.println("Child is desdroyed.");super.desdroy();}public static void main(String[] args) {Child c = new Child();c.display();c.desdroy();// Parent is displayed.// Child is desplayed.// Child is desdroyed.// parent is desdroyed.}
}

在重写(覆盖)父类方法的时候,的确需要考虑是否要使用super调用父类同名方法,但需要注意的是,调用顺序很重要。比如像上面的例子,display代表一种创建行为,所以子类的display动作可能需要在父类的display动作完成后才能进行,所以要先调用super.display()。而destroy方法代表一种销毁动作,是display的反面,所以子类的destroy动作需要在父类的destroy动作执行前进行,这样才能确保某些关联关系在父类销毁前都被正确销毁。

this类似,super还可以用于调用父类的构造函数:

package ch4.inheritence3;import util.Fmt;class Parent {private int number;public Parent(int number) {this.number = number;Fmt.printf("Parent(%d) is called.\n", number);}}public class Child extends Parent {public Child(int number) {super(number);Fmt.printf("Child(%d) is called.\n", number);}public static void main(String[] args) {Child c = new Child(1);// Parent(1) is called.// Child(1) is called.}
}

这种情况下super同样存在着类似于this的限制,即必须在子类构造函数的第一行使用,且单个构造函数内只能使用一次。

初始化基类

存在继承关系的类,其实例的构建过程是自内向外的,有点像是“洋葱结构”。

这里用学习OOP时常用于举例的ShapeRectangleSquare来说明。

Shape是所有图形的基类,代表一种抽象的图形,Rectangle是矩形,意味着有两条可能相等也可能不相等的边,以及二维坐标上的一个点(可以是中心点,也可以是某个角),Square是正方形,是某种“特殊的矩形”,它的四条边是相等的。

package ch4.inheritence4;class Pointer {private int x;private int y;public Pointer(int x, int y) {this.x = x;this.y = y;}}class Shape {public Shape() {System.out.println("Shape is build");}
}class Rectangle extends Shape {private int xEdge;private int yEdge;private Pointer center;public Rectangle(int xEdge, int yEdge, Pointer center) {super();this.xEdge = xEdge;this.yEdge = yEdge;this.center = center;System.out.println("Rectangle is build");}
}public class Square extends Rectangle {private int edge;private Pointer center;public Square(int edge, Pointer center) {super(edge, edge, center);this.edge = edge;this.center = center;System.out.println("Square is build");}public static void main(String[] args) {Pointer center = new Pointer(0, 0);int edge = 10;Square s = new Square(edge, center);// Shape is build// Rectangle is build// Square is build}
}

可以看到,要创建子类实例,需要“由内向外”,先创建基类的实例,再创建父类的实例,最后再创建子类的实例。

需要说明的是,其实Rectangle的构造方法中不需要显式调用super(),因为其父类构造方法是一个不带参数的默认构造方法,这种情况下即使不显式调用super(),Java编译器也会自动调用。但是显式调用父类构造方法依然是一个良好习惯,可以减少一些阅读代码时的歧义。

代理

实际上代理是一种概念,一种设计模式,关于它的详细说明可以见设计模式 with Python 11:代理模式 - 魔芋红茶’s blog (icexmoon.xyz)。

如果不那么严谨地定义代理,实际上当一个对象持有另一个对象时,某种程度上说,当前对象就可以充当所持有的对象的代理。换言之,我们可以通过组合来实现一种简单的代理关系:

package ch4.proxy;class MyClass{public void print(String str){System.out.println("print() is called.");System.out.println(str);}
}class MyClassProxy{MyClass mc = new MyClass();public void print(String str){mc.print(str);}
}public class Main {public static void main(String[] args) {MyClassProxy mcp = new MyClassProxy();mcp.print("hello");// print() is called.// hello}
}

实际上,完整代理的核心实现也是通过组合的方式,只不过可能需要用到接口或抽象基类。

组合和继承

确保正确清理

既然创建实例时是“由内向外”的,是有依赖性的,自然的,销毁实例时也应当是“由外向内”的,只有这样才能确保销毁内部实例时不会依然存在外部实例对内部实例的引用关系。

这里依然用“图形家族”进行说明:

package ch4.inheritence5;import util.Fmt;class Pointer {private int x;private int y;public Pointer(int x, int y) {this.x = x;this.y = y;}@Overridepublic String toString() {return Fmt.sprintf("Pointer(%d,%d)", this.x, this.y);}public void destory() {String thisStr = this.toString();this.x = 0;this.y = 0;Fmt.printf("%s is destroy.\n", thisStr);}
}class Shape {public Shape() {System.out.println("Shape is build");}public void desdroy() {System.out.println("Shape is destroy.");}
}class Rectangle extends Shape {private int xEdge;private int yEdge;private Pointer center;public Rectangle(int xEdge, int yEdge, Pointer center) {super();this.xEdge = xEdge;this.yEdge = yEdge;this.center = center;Fmt.printf("%s is build.\n", this.getName());}private String getName() {return Fmt.sprintf("Rectangle(xEdge:%s,yEdge:%s,center:%s)", this.xEdge, this.yEdge, this.center);}@Overridepublic void desdroy() {String thisStr = this.getName();this.xEdge = 0;this.yEdge = 0;this.center.destory();Fmt.printf("%s is destory.\n", thisStr);super.desdroy();}
}public class Square extends Rectangle {private int edge;private Pointer center;public Square(int edge, Pointer center) {super(edge, edge, center);this.edge = edge;this.center = center;Fmt.printf("%s is build.\n", this.getName());}@Overridepublic void desdroy() {String thisStr = this.getName();this.edge = 0;this.center.destory();Fmt.printf("%s is destory.\n", thisStr);super.desdroy();}private String getName() {return Fmt.sprintf("Squre(edge:%d,center:%s)", this.edge, this.center);}public static void main(String[] args) {Pointer center = new Pointer(5, 5);int edge = 10;Square s = new Square(edge, center);s.desdroy();// Shape is build// Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is build.// Squre(edge:10,center:Pointer(5,5)) is build.// Pointer(5,5) is destroy.// Squre(edge:10,center:Pointer(5,5)) is destory.// Pointer(0,0) is destroy.// Rectangle(xEdge:10,yEdge:10,center:Pointer(0,0)) is destory.// Shape is destroy.}
}

这个例子说明了如何通过destroy方法“从外向内”将实例进行“注销”。

主要注意的是,在SquareRectangle类中,我使用了一个私有的getName()方法作为获取当前实例字符串形式的方法,而非是一般性的重写toString方法,原因是后者是public的,意味着重写后会被“多态调用”,也就是说输出的结果中并不会出现Rectangle(...) is build的字样,只有Square(...) is build

此外,输出结果中Pointer(0,0) is destroy.也很奇怪,这是因为虽然SqaureRectanglecenter属性都是私有的,不存在继承关系,但实际上它们都是指向同一个Pointer对象的引用,所以实际调用destory方法的时候,是Squarecenter属性先被销毁,此时Pointer对象就变成了Pointer(0,0),相当于Rectangle中的center实例被提前销毁了,这显然是一个bug。

像这种可能会同时被多个对象持有引用的对象,销毁时应当检查是否所有引用都已失效,只有在所有引用都失效的时候才能真正销毁:

package ch4.inheritence6;import util.Fmt;class Pointer {private int x;private int y;private int referenceCounter = 0;public Pointer(int x, int y) {this.x = x;this.y = y;}public void addReference() {this.referenceCounter++;}@Overridepublic String toString() {return Fmt.sprintf("Pointer(%d,%d)", this.x, this.y);}public void destory() {referenceCounter--;if (referenceCounter == 0) {String thisStr = this.toString();this.x = 0;this.y = 0;Fmt.printf("%s is destroy.\n", thisStr);}}
}
...
class Rectangle extends Shape {private int xEdge;private int yEdge;private Pointer center;public Rectangle(int xEdge, int yEdge, Pointer center) {super();this.xEdge = xEdge;this.yEdge = yEdge;this.center = center;center.addReference();Fmt.printf("%s is build.\n", this.getName());}...
}public class Square extends Rectangle {private int edge;private Pointer center;public Square(int edge, Pointer center) {super(edge, edge, center);this.edge = edge;this.center = center;center.addReference();Fmt.printf("%s is build.\n", this.getName());}...public static void main(String[] args) {Pointer center = new Pointer(5, 5);int edge = 10;Square s = new Square(edge, center);s.desdroy();// Shape is build// Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is build.// Squre(edge:10,center:Pointer(5,5)) is build.// Squre(edge:10,center:Pointer(5,5)) is destory.// Pointer(5,5) is destroy.// Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is destory.// Shape is destroy.}
}

当然,这里只是说明如何来正确清理会出现“多重引用”的对象,实际编码中往往会采用更简单的方式:

package ch4.inheritence7;import util.Fmt;...
class Rectangle extends Shape {...@Overridepublic void desdroy() {String thisStr = this.getName();this.xEdge = 0;this.yEdge = 0;this.center = new Pointer(0, 0);Fmt.printf("%s is destory.\n", thisStr);super.desdroy();}
}public class Square extends Rectangle {...@Overridepublic void desdroy() {String thisStr = this.getName();this.edge = 0;this.center = new Pointer(0, 0);Fmt.printf("%s is destory.\n", thisStr);super.desdroy();}private String getName() {return Fmt.sprintf("Squre(edge:%d,center:%s)", this.edge, this.center);}public static void main(String[] args) {Pointer center = new Pointer(5, 5);int edge = 10;Square s = new Square(edge, center);s.desdroy();// Shape is build// Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is build.// Squre(edge:10,center:Pointer(5,5)) is build.// Squre(edge:10,center:Pointer(5,5)) is destory.// Rectangle(xEdge:10,yEdge:10,center:Pointer(5,5)) is destory.// Shape is destroy.}
}

只要将center引用指向new Pointer(0,0)或者null,就相当于释放了对原来对象的引用,而原始的Pointer对象也会在所有引用都失效后,由垃圾回收器来进行回收和清理。如果Pointerdestroy必须在清理时得到执行,可以通过在Java编程笔记3:访问权限控制 - 魔芋红茶’s blog (icexmoon.xyz)中提到的Cleaner类来实现。

名称屏蔽

需要注意的是,对父类方法重写时,必须函数签名完全一致,否则不算是重写:

package ch4.override1;import util.Fmt;class Parent{public void func(int i){Fmt.printf("func(int %d) is called.\n", i);}public void func(char c){Fmt.printf("func(char %s) is called.\n", c);}
}class Child extends Parent{public void func(String str) {Fmt.printf("func(String %s) is called.\n", str);}
}public class Main {public static void main(String[] args) {Child c = new Child();c.func("123");c.func(123);c.func('a');// func(String 123) is called.// func(int 123) is called.// func(char a) is called.}
}

上面这个示例,Child中的func方法实际上并不是对父类func方法的重写,只是添加了一种新的重载而已。

这在有些时候会带来一些潜在bug,比如说你打算重写一个方法,但实际上因为参数类型不同,只是进行了重载。

对此,Java提供一个@override标签:

package ch4.override2;import util.Fmt;...class Child extends Parent{@Overridepublic void func(String str) {Fmt.printf("func(String %s) is called.\n", str);}
}
...

@override标签标注的方法,必须是父类方法的重写,否则的话不会通过编译检查。像上边这种情况就会编译报错。

要说明的是,这个标签并非必须,没有也是不影响程序正常执行的,但使用它可以避免重写方法时可能的bug。

组合or继承

组合和继承的取舍,即什么时候使用组合,什么时候使用继承,是一个相当深入OOP的问题,这往往需要开发者有一定的经验。

开发模式对此有一条准则:多使用组合,少使用继承。原因是虽然他们都有着不错的扩展性,但相比之下,组合更加灵活,而继承往往会受到“基类修改接口后所有的子类不得不做出相应修改”这种困境的影响。而且这种风险会随着继承层次增多后越来越麻烦,所以对于继承,开发模式的建议是不要使用超过3层的继承

更多设计模式方面的内容推荐阅读《Head First设计模式》一书,或者我的系列博文设计模式 with Python - 魔芋红茶’s blog (icexmoon.xyz)。

protected

为了能修改基类的设计,应当尽可能将基类的属性设置为private的。如果子类需要使用父类的private属性,可以为其添加一个protected权限的访问器和修改器:

package ch4.protected1;import util.Fmt;class Parent{private int num;protected int getNum() {return num;}protected void setNum(int num) {this.num = num;}
}class Child extends Parent{public Child(int num) {super();super.setNum(num);}public void display(){Fmt.printf("num:%d\n", super.getNum());}public void clean(){super.setNum(0);}
}public class Main {public static void main(String[] args) {Child c = new Child(10);c.display();c.clean();c.display();// num:10// num:0}
}

当然,这个例子中看不出继承的必要性,仅作为protected使用方式的一种说明。

向上转型

向上转型描述的是在某些情况下,一个导出类的对象可以被当做基类的对象看待。

之所以叫做“向上转型”,是因为在UML图中,通常会将基类放在上方,导出类位于下方,比如之前提到的形状相关类族,其继承关系可以用以下的UML图表示:

因为继承关系是is,所以向上转型是安全的,一个基类的对象是可以在合适的时候当做一个父类对象使用的:

package ch4.cast;public class Main {private static void testShape(Shape shape) {shape.display();shape.desdroy();}public static void main(String[] args) {Pointer center = new Pointer(5, 5);int edge = 10;Square s = new Square(edge, center);Shape[] shapes = new Shape[3];shapes[0] = s;shapes[1] = center;shapes[2] = new Line(new Pointer(5, 5), new Pointer(1, 1));for (Shape shape : shapes) {testShape(shape);}}
}

这里我将Pointer和新添加的Line类也作为了Shape的子类。

篇幅起见这里仅展示部分代码,完整代码见java-notebook/xyz/icexmoon/java_notes/ch4/cast at main · icexmoon/java-notebook (github.com)。

除了这种函数调用传参时的向上转型以外,还可以在任意位置“手动”进行转型:

package ch4.cast;public class Main2 {private static void testShape(Shape shape) {shape.display();shape.desdroy();}public static void main(String[] args) {Pointer center = new Pointer(5, 5);int edge = 10;Shape s = new Square(edge, center);testShape(s);}
}

这里直接使用Shape句柄来承接了一个子类型Square的对象,这同样是可以的。

final

final关键字类似于C/C++中的const关键字,不过略有区别。

final数据

被声明为final的数据,如果是基本数据,其内容不能更改,如果是对象,则不能指向新的引用:

package ch4.final1;class MyInteger{private int num;public MyInteger(int num) {this.setNum(num);}public int getNum() {return num;}public void setNum(int num) {this.num = num;}
}public class Main {public static void main(String[] args) {final int num = 123;// num = 222;final String msg = "hello";// msg = "world";final MyInteger mi = new MyInteger(10);mi.setNum(20);System.out.println(mi.getNum());// 20}
}

需要注意的是,虽然final Stringfinal int看上去很相似,但实际上前者是对象,只不过String对象比较特殊,创建后没法修改。

声明为final的对象内容能否修改,取决于对象的具体设计(比如String就不行),final只会限制引用无法重新指向。

final更常见的用途是与static结合,创建“类常量”:

package ch4.final2;class Colors {public static final int RED = 1;public static final int BLUE = 2;public static final int YELLOW = 3;public static final int BLACK = 4;public static final int GREEN = 5;public static boolean isLight(int code) {switch (code) {case BLUE:case YELLOW:case RED:case GREEN:return true;default:return false;}}
}public class Main {public static void main(String[] args) {System.out.println(Colors.isLight(Colors.GREEN));}
}

这在JavaSE5之前很有用,但在有了enum之后就不再那么有用了。

有些语言要求被constfinal声明的类属性必须在声明的同时初始化,但Java并没有这种限制,只声明但没有初始化的final属性可以被称作“空白final”,这样的“空白final”可以在稍后的任意时刻完成初始化,但必须保证在使用前被初始化过,否则就会产生一个编译错误。

package ch4.final3;class MyClass {private final int num;public MyClass(int num) {this.num = num;}
}public class Main {public static void main(String[] args) {MyClass mc = new MyClass(10);}
}

类似的,也可以将方法的参数指定为final,这样就无法在方法中进行更改:

package ch4.final4;class MyClass {...public void passNum(final int num){// num = 123;}
}
...

实例中注释的部分无法通过编译。

final方法

被声明为final的方法将无法被重写:

package ch4.final5;class Parent {public final void test() {System.out.println("test() is called.");}
}class Child extends Parent {// public void test() {// }
}public class Main {public static void main(String[] args) {}
}

finalprivate的方法都无法被父类重写,但它们有一些区别。从定义上讲,private定义的方法完全对子类是不可见的,对子类而言就像是不存在,所以子类完全可以定义一个同样签名的方法,这是允许的:

package ch4.final6;class Parent {private void test() {System.out.println("Parent.test() is called.");}
}class Child extends Parent {private void test(){System.out.println("Child.test() is called.");}public void callTest(){this.test();}
}public class Main {public static void main(String[] args) {Child c = new Child();c.callTest();// Child.test() is called.}
}

final则不同,它仅仅用于声明方法本身不能被子类重写,当然,final只有和publicprotected结合使用才有意义,如果一个方法被声明为private final,那么没有任何意义。

final的主要用途是将一些“骨架”代码所在的方法设置为不能重写的,比如:

package ch4.final7;class Controller {public Controller() {}public final void request() {this.preRequest();this.doRequest();this.afterRequest();}protected void preRequest() {}protected void afterRequest() {}protected void doRequest() {}
}class IndexController extends Controller {@Overrideprotected void preRequest() {super.preRequest();//检查登录状态}@Overrideprotected void doRequest() {super.doRequest();//加载首页html}
}public class Main {public static void main(String[] args) {IndexController ic = new IndexController();ic.request();}
}

这里是一个简易的Web开发中常见的MVC框架中的Control层代码,其中Controller基类的request就是一个包含了“骨架”代码的方法,在这个方法中调用了三个可以被子类继承的方法preRequestdoRequestafterRequest,这样子类就可以根据需要通过继承相应的方法实现在执行HTTP请求前、后执行一些必要操作,比如读写Cookie或者进行登录检查。

当然这里的示例相当简单,通常这些处理HTTP请求的方法还会传递一些包含HTTP请求信息的参数。

事实上上面这种做法在设计模式中被称为“模版方法模式”,关于这个设计模式的更多说明见设计模式 with Python 8:模版方法模式 - 魔芋红茶’s blog (icexmoon.xyz)。

final类

final作用于类只有一个用途——让类不能被继承。

这样做或许并不常见,因为继承本身就是为了让程序设计更具“弹性”,换句话说让一个类变成final的,无疑是自废武功。所以如果你的确打算这么做,要先经过谨慎思考。

对象初始化顺序

在之前简单提到过对象的初始化顺序:

  1. 加载类,并执行static类变量的初始化。
  2. 执行普通属性的初始化。
  3. 执行构造函数。

可以用以下示例验证:

package ch4.init;import java.util.Random;class MyClass{private int num;private static int snum;private static Random random;static{random = new Random();snum = random.nextInt(100);System.out.println("static members inilize is executed.");}{num = random.nextInt(100);System.out.println("normal members inilize is executed.");}public MyClass(){System.out.println("constructor is called.");}
}public class Main {public static void main(String[] args) {MyClass mc = new MyClass();// static members inilize is executed.// normal members inilize is executed.// constructor is called.}
}

如果涉及继承关系,这个初始化过程会更复杂一些:

package ch4.init2;import java.util.Random;...
class Child extends MyClass {private int num;private static int snum;private static Random random;static {random = new Random();snum = random.nextInt(100);System.out.println("Child's static members inilize is executed.");}{num = random.nextInt(100);System.out.println("Child's normal members inilize is executed.");}public Child() {super();System.out.println("Child's constructor is called.");}
}public class Main {public static void main(String[] args) {Child mc = new Child();// static members inilize is executed.// Child's static members inilize is executed.// normal members inilize is executed.// constructor is called.// Child's normal members inilize is executed.// Child's constructor is called.}
}

可以看到,同样是先加载类定义,不同的是如果有继承关系,需要将所有父类的定义都进行加载。加载完类定义后,需要初始化类变量,初始化过程是“从内向外”,因为子类的类变量可以基于父类的类变量进行定义。所以父类的类变量必须先初始化。然后是初始化父类的普通属性,并调用父类的构造函数。这样父类的实例就初始化完毕,然后再初始化子类的普通属性,并调用子类的构造函数。

谢谢阅读。

Java变成笔记4:复用类相关推荐

  1. java学习笔记7--抽象类与抽象方法

    接着前面的学习: java学习笔记6--类的继承.Object类 java学习笔记5--类的方法 java学习笔记4--类与对象的基本概念(2) java学习笔记3--类与对象的基本概念(1) jav ...

  2. Java技术笔记1:类与对象实例之系统常用类

    待我君临天下,结发与蕊可好.@夏瑾墨 一直在反思最近的时间安排,知识没有总结和积累很容易发生遗忘,如果要让自己在短期内能有大的提升,那就需要每天的知识流输入,减去你生活中看起来也是重要的东西,然而性命 ...

  3. Java学习笔记Day06 工具类及常用算法

    第六章 工具类及常用算法 文章目录 第六章 工具类及常用算法 Java语言基础类 Java基础类库 Object类 概述 toString方法 方法摘要 覆盖重写 equals方法 方法摘要 默认地址 ...

  4. Java学习笔记2——常用类

    目录 1 内部类 1.1 成员内部类 1.2 静态内部类 1.3 局部内部类 1.4 匿名内部类 2 Object类 2.1 getClass()方法 2.2 hashCode()方法 2.3 toS ...

  5. java组合语法_JAVA复用类之组合语法的使用(附源码)

    复用代码是Java众多引人注目的功能之一.但是要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情. 上述方法常为C这类过程型语言所使用,但收效并不是很好.正如J ...

  6. 第21天学习Java的笔记-数学工具类Arrays,Math

    27天! 数学工具类 1.1数组工具类Arrays package Demo2101;/* 注意事项:是Arrays进行调用,不是变量进行调用!!!* java.util.Arrays是一个与数组相关 ...

  7. 第16天学习Java的笔记(标准类,Scanner)

    还有34天 一.定义一个标准的类 package Demo1601;/** 一个标准的类通常要拥有下面四个组成部分:** 1.所有的成员变量都用private关键字修饰* 2.为每个成员变量编写一对儿 ...

  8. 【Java学习笔记一】类和对象

    面向对象程序设计的一个一个重要特点是:封装性. 这里的封装性有两方面含义:一是将有关的数据和操作代码封装在一个对象中形成一个基本单位,各个对象之间相互独立互不干扰,二是将对象中某些部分对外隐蔽,即隐蔽 ...

  9. 自学Java系列 笔记2 高级类特性1

    关键字static(静态的) 在Java类中声明变量.方法和内部类时,可使用关键字static做为修饰符. static标记的变量或方法由整个类(所有实例)共享,如访问控制权限允许,可不必创建该类对象 ...

  10. Effective Java读书笔记---四、类和接口

    四.对于所有对象都通用的方法 15.使类和成员的可访问性最小化 区分一个组件设计得好不好,唯一重要的因素在于,它对于外部的其他组件而言,是否隐藏了其 内部数据和其他实现细节 . 信息隐藏 -----& ...

最新文章

  1. Disruptor 源码阅读笔记--转
  2. 转:vc中如何通过http的post方式上传文件
  3. 如何把本地开发的 SAP UI5 应用部署到 ABAP 服务器上
  4. shiro学习(7):shiro连接数据库 方式二
  5. linux双ip备份,LINUX系统的双网卡双IP(双链路)实现方式
  6. CTF-MISC杂项题2
  7. VFP中轻松绑定 Windows 事件
  8. linux 深度美化,deepin15(Linux) 美化终端 安装zsh+oh-my-zsh及其配置和插件
  9. java项目 分模块管理_java 工程项目模块划分及各模块功能梳理
  10. 基于SSM+Bootstrap+MYSQL演唱会网上订票系统
  11. Jmeter性能测试流程
  12. 网卡-驱动-DMA API-TCP/IP
  13. 利用一阶谓词逻辑求解猴子摘香蕉问题
  14. PB程序中在普通激光打印机上实现条码打印
  15. 主流跨境电商平台有哪些-扬帆牧哲
  16. 电子与计算机工程导论,BGPLUS科研荟萃 | 杜克大学 | 电子工程、计算机工程:电子与计算机工程导论...
  17. 测试学习笔记之--pytest使用和断言处理以及setup,theardown使用
  18. 计算机毕业设计如何制作电子商务网站怎么制作购物网站计算机课程设计电子商城做什么(PHP-ASP.NET-c#-JavaWeb-SSM-SSH-J2EE-springBoot
  19. 企业如何正确选择云服务商
  20. 垃圾分类共享网站的设计与实现(SSM)

热门文章

  1. 服务器ccc认证测试项目,CCC强制性认证包含哪些检测项目?
  2. 算法时代必读——《算法霸权》数学杀伤性武器的威胁
  3. 树莓派4b制作加载的FLASH文件系统并移植busybox
  4. 写今目标自动签退的记录
  5. 低代码在爱奇艺鹊桥数据同步平台的实践
  6. 22、python数据处理虚拟变量的转化
  7. android 脸部识别之3D,这两款安卓手机也支持3D结构光人脸识别
  8. Kaggle: Tweet Sentiment Extraction 方法总结 Part 1/2: 常用方法总结
  9. iOS开发备战金三银四·头条三面记录
  10. 【matplotlib】20.其他图