Reusing Classes

The approach to reuse code in procedural languages like C doesn't work well.

Java accomplishes this by reusing classes. There are two ways:

  • Composition - Simply create objects of existing class inside the new class. It reuses existing types as part of the underlying implementation of the new type.
  • Inheritance - Create a new class as a type of an existing class, and add code to it without modifying the existing class. It reuses the interface.

Composition Syntax

Every non-primitive object has a toString() method, and it is called in special situations when the compiler wants a String but it has an object.

Primitives that are fields in a class are automatically initialized to 0 (false etc.). 

Unlike the primitive type, compiler won't create a default object for every reference, because object initialization is expensive.

  • It will create the object on a heap.
  • Initialization an object will implicitly (explicitly) initialize its parent object.

If an object reference is initialized to null, you will get a runtime error if you try to call methods for any of them. However, you can still print a null reference without throwing an exception.

Inheritance Syntax

It is true that you are always doing inheritance when you create a class, because unless you explicitly inherit form some other class, you implicitly inherit from Java's standard root class Object.

A general rule for inheritance is to make all fields private and all methods public (protected) in base class.

To call the method from the base class, you need to use the Java super keyword, which refers to the "superclass" that the current class inherits.

Initializing The Base Class

When you create an object of the derived class, it contains within it a subobject of the base class.

  • The way to guarantee the base class suboject be initialized correctly is to perform the initialization in the derived class constructor by calling the base class constructor. Java automatically do this if the base class constructors has no arguments (default constructor)l
  • If the base class constructors has arguments, compiler doesn't know what arguments to pass. In this case, you must explicitly write a call to the base class constructor using the super keyword, and it must be the first thing to do in the derived class constructor. 

Delegation

A third relationship between classes, this is a midway between inheritance and composition. You place a member object in the class you are building, abut at the same time you expose all the methods from the member object in your new class.

// reusing SpaceShipControls.java

public class SpaceShipControls {
  void up (int velocity) {}
  void down (int velocity) {}
  void left (int velocity) {}
  void right (int velocity) {}
  void forward (int velocity) {}
  void back (int velocity) {}
}

// reusing SpaceShipDelegation.java

public class SpaceShipDelegation {
  private String name;
  private SpaceShipControls controls = new SpaceShipControls();
  public SpaceShipDelegation(String name) {
    this.name = name;
  }

  // Delegated methods
  public void up (int velocity) {
    controls.up(velocity);
  }
  public void down (int velocity) {
    controls.down(velocity);
  }
  // public void left (int velocity) {}
  // ..

  public static void main (String[] args) {
    SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector");
    protector.up(100);
  }
}
You can see how the methods are forwarded to the underlying controls object, and the interface is thus the same as it is with inheritance. However, the advantage of delegation is that you can choose to provide only a subset of the methods in the member object.

Java doesn't support delegation, development tools often do. The above example was automatically generated using the JetBrains Idea IDE.

Combining Composition and Inheritance

Java SE5 has added the @Override annotation, which is not a keyword but can be used as if it were. It will prevent you from accidentally overloading when you don't mean to. Because when you mean to override a method, you can choose to add the @Override annotation and the compiler will produce an error message if you accidentally overload instead of overriding.

Choosing Composition VS. Inheritance

Composition is used when you want the functionality of an existing class inside your new class, but not its interface. The user of  your new class sees the interface you've defined for the new class rather than the interface from the embedded object. For this effect, (in general situation) you embed private objects of existing classes inside your new class.

Inheritance is used when you take an existing class and make a special version of it for particular need. It should be used less frequently (when necessary).

protected

The protected key says "This is private as far as the class user is concerned, but available to anyone who inherits from this class or anyone else in the same package."

Upcasting

The act of converting a derived class reference into a base class reference is called upcasting. It is always safe because you are going from a more specific type to a more general type. (The only thing that can occur to the class interface during the upcast is that it can lose methods, not gain them).

When choosing composition or inheritance, you should ask whether you will ever need to upcast from your new class to the base class. If you must upcast, then inheritance is necessary.

The final keyword

In general, the final keyword means "This cannot be changed".

final Data

A constant is useful for two reasons:

  • It can be a compile time constant that won't ever change. In this case, the compiler is allowed the calculation being performed at compile time, eliminating some runtime overhead. (these sorts of constants must be primitives and are expressed with the final keyword) 
  • It can be value initialized at run time that you don't want changed.

A field that is both static and final has only one piece of storage that cannot be changed.

When used with a primitive, final makes the value a constant. While with an object reference, final makes the reference a constant. Once the reference is initialized to an object, it can never be changed to point to another object. However, the object itself can be modified.

Blank final

class Poppet {
  private int i;
  Poppet(int ii) {i = ii;}
}

public class BlankFinal {
  private final int i = 0; // Initialized final
  private final int j; // Blank final
  private final Poppet p; // Blank final reference
  // Blank finals must be initialized in the constructor
  public BlankFinal(int x) {
    j = x;
    p = new Poppet(x);
  }
}

Java allows the creation of blank finals, which are fields that are declared as final but are not given an initialization value. In all cases, the blank final must be initialized before it is used, and the compiler ensures this. You are forced to perform assignments to final either with an expression at the point of definition of the field or in every constructor.

The advantages of blank final:

  • It provides much more flexibility in the use of the final keyword, since a final field inside a class can now be different for each object.
  • At the same time, it retains its immutable quality.

final Arguments

Java allows final arguments, which means inside the method you cannot change what the argument reference points to.

final Methods

The main reason for final methods is to put a "lock" on the method to prevent any inheriting class from changing its meaning. This is for design reasons when you want to make sure that a method's behavior is retained during inheritance and cannot be overridden. It effectively "turns off" dynamic biding, or rather it tells the compiler that dynamic binding isn't necessary.

final and private

Any private methods in a class are implicitly final. Because you can't access a private method, you can't override it. Adding a final specifier to a private method doesn't give any extra meaning.

Overriding can only occur if something is part of the base class interface. That is, you must be able to upcast an object to its base type and call the same method. If a method is private, it isn't part of the base class interface. It is just some code that's hidden away inside the class.

final Classes

An class is final means it cannot be inherited. There might be two reasons: 

  • The design of your class is such that there is never a need to make any changes.
  • Or for safety, security reasons you don't want subclassing.

Fields & Methods

  • The field field of a class is regardless of whether the class is defined as final, so you can choose the field of a final class can be final or not, as you want.
  • However, since final class prevents inheritance, all methods in a final class are implicitly final, because there is no way to override them.

Initialization and Class Loading

In more traditional languages like C++, programs are loaded all at once, as part of the startup process. The process of initialization must be carefully controlled so that the order of initialization of statics doesn't cause trouble. Otherwise, there will be some problems if one static expects another static to be valid before the second one has been initialized.

Java doesn't have this problem. Compiled code for each class exists in its own separate file, and it won't be loaded until the code is needed. In general, you can say "class code is loaded at the point of first use." Since the constructor is also a static method even though the static keyword is not explicit. So to be precise, a class is first loaded when any one of its static members is accessed.

class Insect {
  private int i = 9; // 5
  protected int j; // 6
  Insect() { // 7
    print("i = " + i + ", j = " + j);
    j = 39;
  }
  private static int x1 = printInit("static Insect.x1 initialized"); // 1
  static int printInit(String s) { // 2
    print(s);
    return 47;
  }
}

public class Beetle extends Insect {
  private int k = printInit("Beetle.k initialized"); // 8
  public Beetle() { // 9
    print("k = " + k);
    print("j = " + j);
  }
  private static int x2 = printInit("static Beetle.x2 initialized"); // 3
  public static void main(String[] args) { // 4
    print("Beetle constructor"); // 
    Beetle b = new Beetle();
  }
}
/* Output:
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
*/

  1. When you run Java on Beetle, the first thing that happens is that you try to access Beetle.main() (a static method), so the loader goes out and finds the compiled code for the Beetle class (Beetle.class). In the process of loading it, the loader notices that it has a base class (informed by the extends key word), which is then load. This happens whether or not you are going to make an object of that base class.
  2. Next, the static initialization in the root base class is performed (1~2), and then the next derived class (2~4). This is important because the derived class static initialization might depend on the vase class member being initialized properly.

At this point, the necessary classed have all been loaded so the object can be created.

  1. First, all the primitives in this object are set to their default values and the object references are set to null. (5~6)
  2. Then the base class constructor will be called. It goes through the same process in the same order as the derived class constructor (7). After the based class constructor completes, the instance variables are initialized in textural order. (8)
  3. Finally, the rest of the body of the constructor is executed. (9)

















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值