第七章 复用类
1.组合
概念:在新类中产生现有类对象。
PS:每一个类都有一个toString()方法,而且当编译器需要一个String时而代码中却只有一个对象,该方法就会被调用。例如:Student stu = new Student(); System.out.print("Student info:"+stu);
1.1 组合语法对象初始化位置
1.定义对象的地方
String s = "string";
2.在类的构造器中
3.惰性初始化class A{ private String name; public String toString(){ return name; } } class B{ private A a; public String lazy(){ a = new A(); } } public class LazyTest{ public static void main(String[] args){ B b = new B(); //使用时才去初始化对象a b.lazy(); System.out.print(b.a); } }
4.实例初始化
Student stu = new Student();
2.继承
2.1 无参构造器
当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象和你用基类直接创建出来的是一样的,它们二者唯一的区别是一个来自外部,一个被包装在导出类内部。
从下述代码可以看见,类的构建过程是从基类开始往外扩散的。所有基类在导出类构造器可以访问它之前,就已经完成了初始化。即使不为类创建构造器,编译器也会合成一个默认的无参构造器,该构造器会调用基类构造器。
public class ConstructorTest extends B{ public ConstructorTest() { System.out.println("This is class ConstructorTest"); } public static void main(String[] args) { ConstructorTest constructorTest = new ConstructorTest(); } } class A { public A(){ System.out.println("This is class A"); } } class B extends A{ public B(){ System.out.println("This is class B"); } } /*output: This is class A This is class B This is class ConstructorTest */
从下述代码可以看出,实例化对象时,类先加载属性,然后才是构造方法。
public class Test5 { public static void main(String[] args) { C c= new C(); } } class A{ public A() { System.out.println("This is class A"); } } class B{ public B() { System.out.println("This is class B"); } } class C extends A{ private B b = new B(); public C() { System.out.println("This is class C"); } } /*output: This is class A This is class B This is class C */
2.2 带参构造器
如果基类没有默认的无参构造器,或者想调用一个带参构造器,导出类的构造器中就需要显示的使用super关键字调用基类构造器并匹配合适的参数列表。
3.代理
类似于实现接口,将方法的实现细节封装起来。
public class Test11 { public static void main(String[] args) { MyServiceImpl myServiceImpl = new MyServiceImpl(); myServiceImpl.up(); myServiceImpl.down(); } } class MyService{ public MyService() { } void up(){ System.out.println("up"); } void down(){ System.out.println("down"); } } class MyServiceImpl{ private MyService myService = new MyService(); public MyServiceImpl() { } void up(){ myService.up(); } void down(){ myService.down(); } } /*output: up down */
4.名称屏蔽(方法重载、重写)
如果基类拥有多个同名重载方法,并不影响导出类新建同名重载方法。但是如果导出类重载方法参数列表与基类重载方法一致,那么导出类会重写(@Override)该方法而不是重载(@Overload)。如果添加了@Override注解,但是基类或没有同样参数列表的同名方法,那么编译器会报错。例:
public class OverloadTest { public static void main(String[] args) { People p = new People(); p.talk(); System.out.println(p.talk("s")); System.out.println(p.talk(1)); System.out.println(p.talk(1.0)); } } class Human{ void talk(){ System.out.println("Human talk"); } int talk(int i){ return i; } double talk(double d){ return d; } } class People extends Human{ String talk(String s){ return s; } /*重写基类方法*/ @Override void talk(){ System.out.println("people talk"); super.talk(); } } /*output: people talk Human talk s 1 1.0 */
7.protected关键字
protected关键字用来为导出类提供基类的访问权限,同时protected还拥有包访问权限。
8.向上转型
概念:
把导出类引用转换为基类引用的动作称为向上转型。由于导出类是基类的超集,基类拥有的属性方法导出类都会有,所以向上转型总是很安全的。在向上转型的过程中,类接口唯一可能发生的事就是丢失方法,而不是获取他们。
9.final关键字
final可能会在数据、方法和类上使用到,每个地方都有不同的含义。
9.1 final数据
1.用final声明数据时,指明该数据为常量。(final修饰的数据在新建对象时初始化)
2.在对常量进行定义时必须对其赋值。(可以在常量的定义处赋值或者每个构造器中赋值)
3.使用static final 定义的常量为编译时常量。(static final修饰的数据在装载时就被初始化且不可改变)
使用static final修饰的数据并不会因为新建对象而改变,因为它在加载类的时候就已经被初始化并指向固定地址。使用final修饰的数据并不能一定在编译时就知道该数据的值,因为它在新建对象的时候才被初始化。例如:
public class FinalTest { private static Random rand = new Random(); private final int A = rand.nextInt(); private static final int B = rand.nextInt(); public static void main(String[] args) { System.out.println(new FinalTest()); System.out.println(new FinalTest()); System.out.println("B="+B); } @Override public String toString() { return "A=" + A; } } /*output: A=229042376 A=-486992066 B=-267339427 */
9.2 final参数
使用final来修饰参数列表中的参数,意味着你对传进来的参数只有只读权限,不可更改。这一特性主要用来向匿名内部类传递数据。
9.3 final方法
使用final修饰方法,表明该方法不能被覆写。
9.4 final类
使用final修饰类,表明该类不能被继承。
10.初始化及类的加载
类加载通常发生于创建类的第一个对象时。但是当访问类中的static域(成员变量)或者static方法时,类也会被加载。
10.1 继承与初始化
下述代码表明了当运行LoadOrder.main()方法时,加载器会启动并找出LoadOrder类的编译代码(.class文件),在对它进行加载时发现它有一个基类,于是它对基类进行加载,加载First.x1的时候,为了获取其值,执行了printInit()方法。然后再加载LoadOrder类,加载LoadOrder.x2时至性printInit()方法。加载完后执行LoadOrder.main()方法中的语句,新建对象时先执行基类构造器,再执行导出类构造器。
public class LoadOrder extends First{ private int k = printInit("LoadOrder.k initialized"); public LoadOrder() { System.out.println("k="+k); System.out.println("j="+j); } private static int x2=printInit("static LoadOrder.x2 initialized"); public static void main(String[] args) { System.out.println("LoadOrder constructor"); LoadOrder f = new LoadOrder(); } } class First{ private int i = 9; protected int j; public First() { System.out.println("i="+i+",j="+j); j=39; } private static int x1=printInit("static First.x1 initialized"); static int printInit(String s){ System.out.println(s); return 47; } } /*output: static First.x1 initialized static LoadOrder.x2 initialized LoadOrder constructor i=9,j=0 LoadOrder.k initialized k=47 j=39 */
总结:无论是加载类还是初始化类,始终会先加载基类(根基类),然后才是一个一个的导出类。初始化类也会从基类开始按顺序加载属性。