p1,p2 - represents the line points...the issue is that I want to use those points in some of the object's method.
Then you must declare them outside the constructor; if you declare them inside the constructor, they're local variables. Like local variables in any function, they aren't in scope outside the function. ("function" = "constructor or method")
You can initialize them inside the constructor, but you must declare them outside:
public class Line extends Shape {
private Point p1; // You may or may not want the `private`, it...
private Point p2; // ...depends what you're going to do with them
public Line(int x1, int x2, int y1, int y2, Color myColor) {
super(x1, x2, y1, y2, myColor);
this.p1 = new Point(this.getX1(),this.getY1());
this.p2 = new Point(this.getX2(),this.getY2());
}
// ...
}
Note: Accessing instance fields such as the above can be done with or without the this.
in front. (E.g., this.p1 = new ...
and p1 = new...
are both valid.) I always use this.
so that it's easy to tell, looking at the code, whether I'm using an instance field or a local variable; my IDE can also help me out more with autocomplete. I think I'm probably in the minority there, though.
Regarding your question about whether to initialize outside the constructor or inside, let's talk about when the initialization you write outside the constructor is actually done, which can be non-obvious:
class A {
int x;
int y;
public A(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
}
class B extends A {
private ImmutablePoint p = new ImmutablePoint(this.getX(), this.getY());
public B(int x, int y) {
super(x, y);
System.out.println("Hello from B");
}
public Point getP() {
return this.p;
}
}
It looks like the initialization happens before the constructor is called (to me, anyway). That isn't what happens. The bytecode the compiler produces for class B
looks like this instead:
// Reconstituted version of the bytecode for our B above
class B extends A {
private ImmutablePoint p;
public B(int x, int y) {
super(x, y);
this.p = new ImmutablePoint(this.getX(), this.getY()); // <== Inserted
System.out.println("Hello from B");
}
public Point getP() {
return this.p;
}
}
Note how it inserted your initialization logic into the constructor.
Here are some reasons for writing your initialization in the constructor rather than as part of the declaration:
If you needed to use information that was only available inside the constructor (an argument that doesn't become part of the object state), you have no choice, you have to do the initialization in the constructor. For instance, there's no way for A
to initialize its x
field except with its x
argument.
If you needed to do initialization differently in different constructors. Again, you'd have no choice but to do the initialization in the constructor rather than with the declaration.
(This is subjective.) Clarity. Since the initialization code is run after the call to super
and before anything else in your constructor, writing it there in the source code can aid clarity.
There's an argument for writing your initialization outside the constructor, with the declaration:
- If you have multiple constructors and the initialization logic is identical for all of them, writing it with the declaration lets you write it only once and reuse it. (You could also do that by calling a method within your constructor, but some people frown on that.) There's an alternative do this, however, which I'll flag up below.
Let's look at that in the context of B
:
class B extends A {
private ImmutablePoint p = new ImmutablePoint(this.getX(), this.getY());
public B(int x, int y) {
super(x, y);
System.err.println("Hello from B");
}
public B(int x, int y, String msg) {
super(x, y);
System.out.println(msg);
}
public Point getP() {
return this.p;
}
}
Now we have two constructors that do slightly different things (System.err
vs. System.out
, using an argument or using a default message.) The bytecode the compiler produces actually does this:
// Reconstituted version of the bytecode for our B above
class B extends A {
private ImmutablePoint p;
public B(int x, int y) {
super(x, y);
this.p = new ImmutablePoint(this.getX(), this.getY()); // <== Inserted
System.err.println("Hello from B");
}
public B(int x, int y, String msg) {
super(x, y);
this.p = new ImmutablePoint(this.getX(), this.getY()); // <== Inserted
System.out.println(msg);
}
public Point getP() {
return this.p;
}
}
So there's arguably a benefit to writing that initialization in one place at the declaration.
It's a style choice; an alternative to it is to use a common denominator constructor:
class B extends A {
private ImmutablePoint p;
private B(int x, int y) {
super(x, y);
this.p = new ImmutablePoint(this.getX(), this.getY());
}
public B(int x, int y) {
this(x, y);
System.err.println("Hello from B");
}
public B(int x, int y, String msg) {
this(x, y);
System.out.println(msg);
}
public Point getP() {
return this.p;
}
}
Finally: Java also has initializer blocks, which are initializer code written outside of any constructor, but in a very method-like way:
class B extends A {
private ImmutablePoint p;
// Instance initializer block:
{
this.p = new ImmutablePoint(this.getX(), this.getY());
}
public B(int x, int y) {
super(x, y);
System.err.println("Hello from B");
}
public B(int x, int y, String msg) {
super(x, y);
System.out.println(msg);
}
public Point getP() {
return this.p;
}
}
You can have limited logic within initializer blocks.
So you have all sorts of options. Other than when you're required to initialize in a constructor (#1 or #2 on my earlier list), it's a style choice.