07-复用类
简介
复用类顾名思义就是利用已有的代码实现某功能,每个人都不想不断做重复性的工作,复用类就是把之前的代码通过组合或者继承等方法重新利用起来,利用复用类不但可以使代码更加简洁,灵活性也能得到很高的提升。
1,组合语法
组合就是将基本类型,String类型或者对象引用作为某个类的成员变量,通过引用变量控制其个自功能,例如实现个汽车类,可以把引擎类作为成员变量引用,然后引擎类就可以给汽车类提供引擎功能使用了。
public class Car { private String name,version; //初始化给变量赋值 private int size=10; private CarEngines engines; //构造器给变量赋值 public Car(String name,String version,int size,CarEngines engines){ this.name=name; this.version=version; this.size=21; engines=new CarEngines(); } //代码块赋值 { this.name="qq"; this.version="zx1.0.0"; this.size=12; engines=new CarEngines(); } //利用引擎类启动 public void run(){ engines.run(); } //利用引擎类关闭 public void close(){ engines.close(); } //set方法给变量赋值 public void setName(String name) { this.name = name; } public void setVersion(String version) { this.version = version; } public void setSize(int size) { this.size = size; } public void setEngines(CarEngines engines) { this.engines = engines; } } /** * 汽车引擎 */ class CarEngines{ public void run(){ System.out.println("启动汽车"); } public void close(){ System.out.println("关闭汽车"); } }
从代码示例中可以看到,给变量赋值有很多种方式,根据示例情况赋值,例如set方法可以在运行中为某个实例赋n次值,而通过构造器只能为某个实例赋一次值。
2,继承语法概念
java中如果需要A类复用B类提供可以共用的方法,可以通过extends继承B。那么A就拥有了B的部分功能(只能继承B访问权限允许的域和方法) ,java的所有对象隐式父类都是Object,除非明确指定继承其他类,一个子类只能继承一个父类,不能重复继承。
如果B类构造方法带有参数,那么A类构造器必须使用supper指定要执行的B类构造器,不然会报错.示例如下:
//父类 public class Father { private String name="lalalala"; public int size; public String common="f1"; public Father(int i){ System.out.println(i); } public void print(){ System.out.println("name:"+name); } public void printSize(){ System.out.println("fffff"); } } //子类 public class Child extends Father { public String common="c1"; public Child(){ super(1);//必须执行,不然会报错, /** * 参数的时候需要明确执行,无参的时候是程序隐式执行父构造器 * 也就是说子类构造器构造执行时必须先执行父构造器,不然就会报错 */ } public void printName(){ //可以直接调用父类方法 print(); } @Override public void printSize(){ System.out.println("ccccc"); } public void setSize(){ this.size=123;//可以直接使用父类允许访问权限的域和方法 //this.name 那么在父类中设置为private私有的, 所以子类服务继承使用 } /** * 父类和子类同时拥有的方法和变量名称时,在通过子类调用时如下: * 默认调用则是调用子类的域和方法,默认调用其实也是this调用 * 指定super调用则是调用父类的域和方法。 * 通过this调用则是调用子类本身的域和方法。 */ @Test public void show(){ System.out.println("defult:"+common+",father:"+super.common+",child:"+this.common); printSize(); super.printSize(); this.printSize(); //结果: //defult:子类.common,father:父类.common,child:子类.common //子类:printSize方法 //子类:printSize方法 //父类:printSize方法 } }
注意。初始化的时候先初始化父类的静态(按顺序),再初始化子类的静态(按照顺序),再初始化父类的成员变量,构造块,最后再初始化父类的构造器,之后再初始化子类的成员变量,构造块,再初始化子类的构造器。详情请看笔记《02-方法传参和初始化与垃圾回收清除》初始化部分。
3,向上转型
在java语言中,子类可以认为是父类的一种类型,是is-a的关系。向上转型的概念指的是,把子类当做父类使用,可以使用父类类型引用指向子类实例,调用父与子共有的方法和域,例如,A类继承B类, 有个方法method(A a),而将B传入method方法作为参数java支持,这就是向上转型。如下:
//方法参数是父类类型 public static void printSizeByFather(Father f){ f.printSize();//输出结果是子类实例内容,即“子类:printSize方法” } public static void main(String[] args) { printSizeByFather(new Child());//传入的子类实例 }
父类引用可以指向子类实例,也可以将父类作为参数子类传入参数,java通过RTTI类判断具体是哪个子类实例还是父类实例。
4,final关键字
final的概念如下:
1,一个永远不会被改变的常量
2,运行时被初始化的值,而不会被改变。
编译期常量在java中必须是基本数据类型,String应该也可以。并且以final关键字修饰,在定义时必须赋值。这就是编译期常量,比编译期常量的好处是在编译期时候就将“编译器常量”赋值分配空间。减轻一些运行期的负担,一个既是static又是final的变量域只占据一块不能改变的储存空间。如果想看编译期和运行期区别就看之前的笔记《java中编译器和运行期的区别》。
4.1 空白final常量
从final概念就知道final是修饰的变量值一旦赋值后就不能修改,引用是不能修改指定的位置,对象本身可以修改,只是针对引用角度来说不能被修改。那么如果final一开始没有赋值,怎么处理?正常情况下如果存在有final修饰的变量且没有赋值编译器会报错。
以下示例:
/** * TODO 测试final空白变量(什么final并没实现赋值)什么时候可以赋值。 * 结果在普通方法中无法赋值,而再构造器方法中可以赋值 */ private final Hello hello; private final Hello hello2=new Hello(); //普通方法 public void isMethodCreateFinal(){ //hello=new Hello(); 编译不通过。无法赋值 } //构造器方法 public Handler(){ hello=new Hello();//可以赋值 //hello2=new Hello(); 构造器只能对未赋值的final变量赋值,但不能改值,编译器报错 }
final的空白变量可以用构造器赋值。但是已经赋值的final常量则不能再更改了,而其他方法时不能给构造器赋值,原因是因为构造器只会执行一次,也符合final变量不能改变的原则。
final实现不可变对象,示例:
//可变对象 public class Handler { private int n; public Handler(int n){ this.n=n; } public void show(){ if (n!=n){ System.out.println("线程不安全"); } } } //不可变对象 class HandlerFinal{ private final int n; public HandlerFinal(int n){ this.n=n; } public void show(){ System.out.println("线程安全"); } }
对象可变与否,是看对象的状态即变量是不是可以共享以及可以被修改,示例中Handler类和Handler类只有一个基本类型的n变量,而且更改n变量只能通过构造方法才可以,但由final修饰的n才是不可变对象,对象实例化操作不是原子性的,上面有替代new对象时,会分为好几个步骤,比如先初始化静态变量以及父类变量等等,最后才实例化对象。试想下,但java对final修饰的变量做了特殊处理,导致final修饰的变量可以实现其不可变性,即线程安全。
4.2 final参数
示例如下:
/** * TODO final 方法参数 * @param i */ public void finalParam(final Integer i){ System.out.println(i); //i=new Integer(3);可以读但无法修改final参数,否则编译器不通过,常用于匿名内部类 } @Test public void finalParamRun(){ /** * 可以正常传不同值 */ finalParam(new Integer(3)); finalParam(new Integer(5)); }
此时的方法参数map由final修饰,就是final方法。final方法参数能够传不同的值,但不能修改参数,否则报错
4.3 final方法
设计final修饰方法有两个原因:1,final修饰的方法不能被覆盖,为了避免父类方法被覆盖导致方法逻辑破坏,但需要可以继承,可以通过final禁止被覆盖。原因2已经失效,但可以提下,在之前的final方法是jvm优化的一种方式,final修饰的方法能够提高性能。但java已经逐渐的抛弃了这种优化方案,所以原因2已经不再重要。而现在如果使用final修饰方法的话,主要原因是防止方法该方法被覆盖。
示例:
class FinalMethod{ public final void finalMethod(){ System.out.println(); } public void method(){ System.out.println(); } }
尝试覆盖方法:
final和private的区别?
区别1:final修饰的方法不能被覆盖,但是能够继承。子类依然可以调用父类的final方法,但是无法覆盖。而private修饰的方法无法继承,更别提覆盖了。
区别2:final至少一种修饰,还可以对这个方法设定其他的访问权限,如public,default,protected,private等。
另外,private是隐式的final修饰。所以如果再给private 方法添加final修饰,是可以但没有实际意义
不能被覆盖仅仅是final方法,final变量就没有覆盖的概念 了。是可以在子类写重名的成员变量。
4.4 final类
如果不希望自己创建的类有子类即不希望被继承,那么就可以使用final修饰类。 final修饰的类在复用中的作用主要是不能被继承,另外final的类的所有方法都是隐式的final修饰。
格式:
final class FinalClass{ }
尝试继承结果:

5,总结:
向上转型作为是否使用继承和组合的重要因素,利用向上转型可以让代码更加灵活,耦合度更低,在程序中尽量少使用继承,多用组合,面向接口开发把具体实现封装,想要理解的话可以看设计模式:策略模式。

浙公网安备 33010602011771号