javac *.java
CLASSPATH
.class
file, the compiler can also go out looking for corresponding source (.java
) files
and compiles them
g++ *.cpp
is exactly the same as if you had written g++ ....cpp
for each .cpp
file in the directory
#include
'd files into the source file
class C { … void f() { int x; } … private int x;
{}'s
)(, exception-handler parameter)
this
in constructor or set methods - class vs local scope
this
in methods
void f() { … Employee e = new Employee(…); … }
e
is created upon entry to the method; and destroyed upon exit
Employee
object is created when new
is invoked;
when its lifetime ends is not our concern in Java; in C++ it;s is destroyed when
delete
is invoked.
int x, y;
y = 7;
x = y;
// 12 = x; // 12 is not an lvalue
clone
):
class Integer { Integer(int val) {this.val = val;} Integer(Integer integer) {val = integer.val;} // copy constructor … void copy(Integer integer) {val = integer.val;} // in-place copy method … int val; } … Integer i1 = new Integer(3); // uses 'int' constructor Integer i2 = new Integer(12); // uses 'int' constructor Integer i3 = new Integer(i1); // uses copy constructor — clone Integer i4 = i1; // reference assignment — alias i2.copy(i3); // in-place copy method — clone i2 = new Integer(i4); // copy constructor — clone
swap(a, b); // a & b are arguments … void swap(int x, int y) {…} // x & y are parameters
static void swap(int x, int y) { int t = x; x = y; y = t; }
static void swap(String x, String y) { String t = x; x = y; y = t; }
static void swap(int [] arr, int index1, int index2) { int t = arr[index1]; arr[index1] = arr[index2]; arr[index2] = t; }
in this scenario, we are not modifying the parameters — in particular the array reference — but rather elements of the object (i.e., the array) it references
class Pair { Pair(int first, int second) { this.first = first; this.second = second; } public int first, second; // making them public so we can get to them - not relevant to the discussion } Pair pair = new Pair(a, b); swap(pair); a = p.first; b = p.second; static void swap(Pair p) { int t = p.first; p.first - p.second; p.second = t; }
again, we are not modifying the parameter (the object reference), but rather what it is referencing — instance variables of
the object (i.e., the Pair
) it references
int [] arr = {1, 2, 3}; inc(a); … void inc(int [] arr) { for i in 0; i < arr.length; i++) arr[i]++; }
int [] arr = {1, 2, 3}; resize(a); … void resize(int [] arr) { int [] temp = new int [arr.length * 2]; for (int i = 0; i < arr.length; i++) temp[i] = arr[i]; arr = temp; }
int [] resize(int [] arr) { int [] temp = new int [arr.length * 2]; for (int i = 0; i < arr.length; i++) tempi] = arr[i]; return temp; }
int [] arr = {1, 2, 3}; … a = resize(a);
s = s.substring(2, 6);
ArrayList<Integer>
ArrayList<Integer> i_aList = new ArrayList<Integer>(); i_aList.push_back(12); … ArrayList<String> s_aList = new ArrayList<String>(); s_aList.push_back("Hello");
ArrayList alist = new ArrayList();
ArrayList
class, and it has Object
as its element type
vector<int> i_vect; i_vect.add(12); … vector<string> s_vect; s_vect.add("Hello");
class C { … void print(int i) {…} void print(String s) {…} … }
… f(3); // print(int) f("Hello"); // print(String) f(3.5); // ??? f(new Integer(2)); // ???
class C { … int print(int i) {…} double print(int i) {…} … } … int i = f(3); // compile-time error double d = f(3); // compile-time error System.out.println(f(3)); // compile-time error
class C {
…
void print(int i) {…}
…
}
class D extends C {
…
void print(String s) {…}
…
}
class C {
…
void f(int i) {…}
…
}
…
class D extends C {
…
void f(int i) {…}
…
}
C c = new C();
c.f(); // C.f is called
D d = new D();
d.f(); // D.f is called
c = d; // upcast (legal)
c.f(); // D.f is called! … the receiver is a D!
f()
vs f(3)
detectable by compiler
class Sup { … void f() {…} … } class Sub extends Sup { … void f() {…} … } class App { … Sup sup; sup = new Sup(); sup.f(); sup = new Sub(); sup.f(); … }
sup
String
vs ArrayList
String
String
, it is therefore referencing a single value, which is not expected to change
String
object, one should not be able to change the contents of
the String
object as it will affect the value for both references
setCharAt
method for String
. as that would involve an in-place modification to the String
object
String
object
String s = "Hello world!"; s = s.substring(0, 5);
ArrayList
ArrayList
is a container, essentially containing a dynamically sized array
resize
method presented above is at the heart of
the dynamic sizing
ArrayList
to change
ArrayList
of Employee
objects, if we were to iterate the list and give all employees a 10% raise, we
would want that reflected whenever any other part of the system access an employee object.
ArrayList
itself is merely the container, it's the elements that are of real interest
ArrayList
object, modifications made by one to the contents of the ArrayList
object
is (typically) of interest to all
ArrayList
methods are therefore in-place; i.e., they do not create a new container; rather they modify
elements in the receiving container
ArrayListaList = new ArrayList (); aList.add(12);
+
and +=
<<
and >>
==
and !=
System.out
and System.in
??
size
and isEmpty
size
is implemented, isEmpty
can be simply coded as
class ArrayList { int size() {logic to determine number of elements in the container} boolean isEmpty() {return size() == 0;} … }
size
to be abstract at the collection superclass (interface) level, we can then actually provide the above definition
of isEmpty
(which depends on size
) at that level.
abstract class Collection { abstract int size(); boolean isEmpty() {return size() == 0;} // one isEmpty implementation for all Collection (sub)classes … }
class ArrayList extends Collection { int size() {logic to determine number of elements in an ArrayList} … }
class HashSet extends Collection { int size() {logic to determine number of elements in a HashSet} … } …
toString
Methodsclass Student { … public String toString() {return id + " " + name + "\n" + address + "\n" + courses;} … int id; Name name; // each of these classes has its own toString method Address address; ArrayListthecourses; }
toString
method leverages the toString
methods of its component instance variables that are objects
(class types).
toString
of a class from scratch; rather you use the toString
s of the class' instance variables
class C { C(C c) {this.copy(c)} void copy(C c) { this.i = c.i; this.d = c.d; this.s = new String(c.s); // deep copy, since we're in a copy method } int i; double d; String s; }
+
and +=
+
takes two operands and produces a new value, leaving the operands untouched
+=
takes two operands and stores the result in the first operands (the second remains untouched); the value of the expression is the first
operand (containing the result of the operation)
Rational
Classclass Rational { … Rational mul(Rational r) {…} Rational mulInPlace(Rational r) {…} … int num, denom; }
Rational r1 = new Rational(1, 2), r2 = new Rational(3, 4); Rational r3 = r1.mul(r2); // r3 is pointing at a new Rational containing 3/8; r1 and r2 are unchanged r1.mulInPlace(r2); // r1 is still pointing at the same object as before, but the num/denom is now 3/8 r2 = r2.mul(r2); // r2 is pointing at a new (different) Rational object containing 9/16
r1.mul(r2)
is like r1 * r2
(operation produces new value; operands are not modified)
r1.mulInPlace(r2)
is like r1 *= r2
(operation places result in first operand; second is untouched)
size
can't call isEmpty
Rational
class againa c a * c - * - = ----- b d b * d
mul
MethodRational mul(Rational r) { Rational result = new Rational(this.num * r.num, this.denom * r.denom); return result; }
Rational
mulInPlace
MethodRational mulInPlace(Rational r) { this.num *= r.num; this.denom *= r.denom; return this; }
void
would be acceptable; we return the receiver to allow chaining
r1.mulInPlace(r2).mulInPlace(r3)); // r1 *= r2 * r3;
mulInPlace
Leveraging mul
mul
);
mulInPlace
) then leverages mul
Rational mulInPlace(Rational r) { Rational result = this.mul(r); this.copy(result); return this; }
this.copy(result);
copy
method in the class
Rational copy(Rational r) { this.num = r.num; this.denom = r.denom; }
mul
is copied to result
and then
never used again
mul
Leveraging mulInPlace
mulInPlace
);
mul
) then leverages mulInPlace
Rational mul(Rational r) { Rational result = new Rational(this); return result.mulInPlace(r); }
Rational result = new Rational(this);
this
), as it contains the result of the operation
mulInPlace
's Return ValuemulInPlace
to have a return value — the effect of the operation was to modify the receiver —
r1
in the above example r1.mulInPlace(r2)
mul
that leverages mulInPlace
, we see that having a return value is useful:
return result.mulInPlace(r);as opposed to
result.mulInPlace(r); return result;
mul
and mulInPlace
:
r1 = r2.mul(r3.mulInPlace(r4));which is equivalent to:
r1 = r2 * r3 *= r4;
class Employee { … Name name; }
name
defaults to null
(as mentioned above), i.e., it
needs to be initialized to some string before it can be used.
name
prior to such initialization result in a NullPointerException
…
NullPointerException
means that the error occurs as soon as we try to work with
name
; as opposed to working with 'garbage' data which may not result in an error until much
later in the code.
class Employee {
…
Name name; // This poses a problem of initialization
};
name
is an actual Name
object,
not a reference.
class Window {
Window(int width, int height) {
this.width width;
this.height - height;
}
int getWidth() {return width;}
int getHeight() {return height;}
private int width, height;
}
class BorderedWindow extends Window {
Borderedwindow(boolean thinBorder, int width, int height) {
super(width, height);
if (thinBorder) {
borderWidth = getWidth() * .05; // requires that width
of superclass has been initialized
borderHeight = getHeight() * .05;
}
else {
borderWidth = getWidth() * .1;
borderHeight = getHeight() * .1;
}
}
int borderWidth, borderHeight;
}
super
keyword …