Lecture notes on Programming Languages

what is programming language and its types and what is programming language in computer science pdf free download
DanialOrton Profile Pic
DanialOrton,Hawaii,Professional
Published Date:09-07-2017
Your Website URL(Optional)
Comment
Programming Language Concepts: Lecture Notes Madhavan Mukund Chennai Mathematical Institute 92 G N Chetty Road Chennai 600 017 http://www.cmi.ac.in/˜madhavan2Contents I Object-oriented programming 7 1 Data encapsulation with classes 9 1.1 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.2 Data encapsulation (public and private) . . . . . . . . . . . . . . . . . . . . 12 1.3 Static components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.4 Constants (the attribute final) . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.5 Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2 Java Interlude 1 Quick introduction to Java 21 2.1 Structure of a Java program . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.2 Compiling and running Java programs . . . . . . . . . . . . . . . . . . . . . 22 2.3 Basic Java syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.3.1 Scalar variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.3.2 Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2.3.3 Compound statements . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2.3.4 Final note . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3 Subclasses and inheritance 29 3.1 Subclasses and superclasses . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.2 Inheritance polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.3 Multiple inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.4 The Java class hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 3.5 Subtyping vs inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 4 Abstract classes 37 4.1 Abstract Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 4.2 Generic functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 4.3 Multiple inheritance and interfaces . . . . . . . . . . . . . . . . . . . . . . . 40 5 Interfaces: Applications 43 5.1 Callback functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 5.2 Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 35.3 Ports and interaction with “state” . . . . . . . . . . . . . . . . . . . . . . . . 51 6 Java Interlude 2 Parameter passing, cloning, packages 53 6.1 Parameter passing in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 6.2 Cloning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 6.3 Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 6.4 The protected modifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 II Exception handling 59 7 Exception handing 61 7.1 Java’s try-catch blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 7.2 Cleaning up with finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 7.3 Customized exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 7.4 A “cute” fact about finally . . . . . . . . . . . . . . . . . . . . . . . . . . 65 III Event driven programming 67 8 Event driven programming 69 8.1 Programming graphical user interfaces . . . . . . . . . . . . . . . . . . . . . 69 8.2 Some examples of event driven programming in Java . . . . . . . . . . . . . 71 8.2.1 A button. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 8.2.2 Three buttons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 8.3 The event queue. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 8.4 Custom events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 IV Reflection 81 9 Reflection 83 V Concurrent programming 89 10 Concurrent Programming 91 10.1 Race conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 10.2 Protocols based on shared variables . . . . . . . . . . . . . . . . . . . . . . . 94 10.3 Programming primitives for mutual exclusion . . . . . . . . . . . . . . . . . 96 10.4 Monitors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 10.5 Monitors in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 10.6 Java threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 410.7 Interrupts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 VI Functional programming 109 11 Functional programming in Haskell 111 11.1 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 11.2 Defining functions in Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 11.3 Functions as first class objects . . . . . . . . . . . . . . . . . . . . . . . . . . 115 11.4 Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 11.5 Conditional polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 11.6 User defined datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 11.7 Rewriting as a model of computation . . . . . . . . . . . . . . . . . . . . . . 120 11.8 Reduction strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 11.9 Outermost reduction and infinite data structures . . . . . . . . . . . . . . . . 123 12 The (untyped) lambda calculus 125 12.1 Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 12.2 Life without types. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 12.3 The rule β . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 12.4 Variable capture. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 12.5 Encoding arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 12.6 One step reduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 12.7 Normal forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 ∗ 12.8 → -equivalence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 12.9 Church-Rosser property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 12.10Computability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 12.11Encoding recursive functions in lambda calculus . . . . . . . . . . . . . . . . 135 12.12Fixed points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 12.13A xed fi point combinator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 12.14Making sense of terms without normal forms . . . . . . . . . . . . . . . . . . 139 13 Introducing types into the lambda calculus 141 13.1 Simply typed lambda calculus . . . . . . . . . . . . . . . . . . . . . . . . . . 141 13.2 Polymorphic typed calculi . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 13.3 Type inference as equation solving . . . . . . . . . . . . . . . . . . . . . . . 145 13.4 Unification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 13.5 Type inference with shallow types . . . . . . . . . . . . . . . . . . . . . . . . 149 VII Logic programming 153 14 Introduction to logic programming 155 14.1 Facts and rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 514.2 Computing in Prolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 14.3 Complex structures in Prolog . . . . . . . . . . . . . . . . . . . . . . . . . . 157 14.4 Turning the question around . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 14.5 Arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 14.6 Negation as failure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 14.7 Cut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 VIII Scripting languages 163 15 Programming in Perl 165 15.1 Scalar datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 15.2 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 15.3 Control flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 15.4 Input/Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 15.5 Matching and regular expressions . . . . . . . . . . . . . . . . . . . . . . . . 169 15.6 Associative arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 15.7 Writing functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 15.8 Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 IX Appendix 175 A Some examples of event-driven programming in Swing 177 A.1 Multicasting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 A.2 Checkbox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 6Part I Object-oriented programming 7Chapter 1 Data encapsulation with classes The starting point of object-oriented programming is to provide a more faithful implemen- tation of the notion of “type” in programming. The programming languages we are familiar with come with standard built-in types that we can assign to variables and values: For example, in C we can use int, float, double, char, .... In Haskell, similarly, we can use Int, Float, Char, Bool, .... In addition, programming languages provide a way to assign a single name to a collection of values of the same type. The nature of this collection is determined by the underlying architecture assumed by the programming language. Since C reflects the standard von Neumann stored program architecture, we have arrays that provide “random” (i.e. equal time) access to each element of the collection, while Haskell, whose semantics is defined with respect to an idealized functional programming architecture, provides us with lists, which have “sequential access” (getting to the ith item takes time proportional to i) but which come with a number of useful decomposition functions that make inductive definitions easy to implement. However,noprogramminglanguagecanexpecttopredefinealltheuseful“types”thatwe might need in a particular application—we might want to work with stacks or binary search trees or directed acyclic graphs or ...Typically, we define these in terms of the existing primitives available. For instance, in Haskell we might represent a stack as a list or, for efficiency, as a pair of lists, while in C we might represent a stack as an array or, if we want to let the stack grow arbitrarily large, as a linked list. Regardless of the implementation, we expect a stack to be used in a particular way. Additions to the stack are done using a function “push”, deletions by a function “pop” and the only enquiry that we can make about the state of the stack is the question “is-the-stack- empty”. Suppose the stack is defined as int s100, top_of_stack = 0; in C and the stack grows as s0, s1, .... When the stack has 10 elements (i.e., top_of_stack == 10) we do not want a function to access, say, s7. 9In a language like C, this “integrity” of the datatype stack is difficult to enforce. It can, in general, be achieved only through disciplined programming—there is no way to guarantee that a stack is never misused. The preceding discussion on stacks applies equally to other kinds of complex datatypes like binary search trees, directed acyclic graphs etc. In the jargon of programming language theory, these are all examples of Abstract Data Types—data structures that have certain fixed functions to manipulate them and these functions are expected to be rigidly adhered to as the only means of accessing the underlying data, regardless of the implementation. 1.1 Classes To facilitate the definition of abstract data types, the programming language Simula (1967) introduced a concept called a class. (The word class is not important in itself, except that it has stuck and so is now a standard term in the field.) A class is a “type definition” consisting of two parts: 1. How the data is stored in this type. 2. What functions are available to manipulate this data. For instance, we might represent a stack of integers by the following class definition: class stack int values100; / values are stored in an array / int tos = 0; / top of stack, initialize to 0 / push (int i, ...) / push i onto stack / valuestos = i; tos = tos+1; / Should check that tos 100 / int pop (...) / pop top of stack and return it / tos = tos - 1; / Should check that tos 0 / return valuestos; bool is_empty (...) / is the stack empty? / return (tos == 0); / yes iff tos is 0 / At some places in this hypothetical definition, the function parameters have been left unspecified and denoted “...”. We will fill in these blanks shortly. We can use the class name as a new type in the language and define variables of this type elsewhere in the program, as follows: 10stack s,t; These definitions only provide the capability for s and t to denote stacks. No storage is allocated at this point. They correspond to a pointer declaration of the type int p in C, which gives p the ability to point to a memory location holding an integer but does not, in itself, allocate a valid memory location for p to point to. In order to get a “real” stack, we have to ask for it. For instance, we might say: s = new stack; t = new stack; This generates two independent stacks, one named s and the other t. If, instead, we had written s = new stack; t = s; we get a single stack with two names, s and t, either of which can be used to access the stack. Theclassdefinitiononlyprovidesa template foradatatype. Theoperation newgenerates an instance ofthetemplate. Theword object inobject-orientedprogrammingissynonymous with the phrase instance of a class. How do we manipulate the objects s and t? Suppose we want to push the value 7 onto the stack s. We do this by writing: s.push(7); Note the difference in style with a conventional language. In C or Haskell, push would take two arguments, the stack itself and the value to be pushed. In the object-oriented framework, each instance of the class has its “own” copy of these functions and is thus an implicit argument to the function. Similarly, to retrieve the value at the top of s into a local variable and to query whether t is empty, we would write the following, respectively: i = s.top(); if (t.is_empty()) ... We can now say what should appear in place of the mysterious ...in the function defini- tions in our class stack—nothing In other words, the functions pop and is_empty take no argumentsatall—theonlyargumenttheyneedisthenameofthestackonwhichtooperate, which is implicitly given since these functions will be called with respect to a fixed stack. Similarly, push needs only one argument: the value to be pushed. Is there any significant difference between a C style function push(s,i) which takes two arguments, one of which is the stack, and the object-oriented version s.push(i), where the 11stack is implicitly passed? Arguably this is only a matter of style and the two approaches have the same “effect”. But, philosophically, the syntax reflects the object-oriented approach: when we create an object (an instance of a class) we are supposed to visualize the object as a black box with a display which can show us some information about the box and some slots where we can insert data. The box is equipped with “buttons”, one for each function. Pushing a button invokes the corresponding function, with the input slots used to slip in parameters to the function, and the value returned shown on the display. Our access to the internal state of the object, whether to observe it or to modify it, is expected to be only via pushing buttons. (Note: More jargon—the functions defined in classes are often called methods and the data items are often called instance variables or instance fields .) 1.2 Data encapsulation (public and private) What is the status of the instance variables defined within a class? It seems desirable that these variables should not be accessible externally. For instance, there should be no way to use the variables s and t of type stack to directly manipulate the contents of tos and the array value within s and t. For, if these internal variables are accessible externally, we once again have to deal with the problem of maintaining the integrity of our abstract data type One way to achieve total data integrity is to make all internal data variable inaccessible from outside. Of course, there is almost always a requirement to be able to access the values of some or all of the variables and also modify or update them—for example, consider the following definition: class date int day, month, year; where one might reasonably expect to be able to find out what date is currently stored, as well as, perhaps, to reset the date to a fresh value. To meet this requirement while keeping internal variables inaccessible, we could write functions like get_date() and set_date(...) that provide limited, controlled access to the data stored in the class. (Note: In object-oriented jargon, a method that reads the values of internal variables, like get_date(), is called an accessor method and a method that updates the values of internal variables, like set_date(...) is called a mutator method.) Using appropriate accessor and mutator functions, we can control very precisely the degree to which the data in the stack can be manipulated externally. However, probably to accommodate lazy programmers, most object-oriented languages do permit direct external access to variables. The syntax is similar to that for accessing members of structures in C (or fields of records in Pascal). For example, the field tos in the stack s is designated s.tos, so we can write code like: if (s.tos == 0) ... 12Once we permit this facility, we have to introduce an additional mechanism to prevent misuse—that is, to retain data integrity. This is typically done by adding the qualifiers public and private to each variable defined in a class. As should be clear, a variable that is declared to be public is accessible from outside while one that is declared private is not. One would write class stack private int values100; private int tos = 0; ... to ensure that the only way to access the stack is via push, pop and is_empty. Of course, private only refers to the external world: the functions within the class like push, pop and is_empty have full access to all the data variables defined in the class. What about methods—should all methods be accessible by default? Suppose our lan- guage has a mechanism to request storage at run-time (like the malloc function in C). We might then incorporate a method extend_stack into our class and use it when the stack becomes full: class stack ... push (int i) / push i onto stack / if (stack_full) extend_stack(); ... / Code to add i to stack / extend_stack() ... / Code to get additional space for stack data / ... Clearly extend_stack is an implementation detail that is not intended to be revealed outsidetheclass. Itwouldmakesensetoextendtheuseoftheattributespublicandprivate to methods as well, making, in this case, only the methods push, pop and is_empty public and all other methods, including extend_stack, private. 131.3 Static components A C program is a collection of functions, all at the same level and hence all on an equal footing. To unambiguously designate where the computation starts, we insist that there is always a function called main where execution begins. In a purely object-oriented language, a program is a collection of classes. Every function and variable that is defined in the program must lie within some class. However, as we have seen, classes are just templates and their definitions come to life only after they are instantiated using new. Initially all classes are inactive and there is no way for the program to get started—effectively, the program is deadlocked. So, there is a need to be able to define functions whose existence does not depend on a class being instantiated. There is another reason for having such functions. Consider, for example, routine func- tions like read and write (or scanf and printf, in C terminology), or, for example, math- ematical functions like sin, sqrt, ...Clearly, it makes no sense to have to artificially instan- tiate a class in order to use such functions. One way to get around this problem without introducing free floating functions that live outside classes (as happens, for instance, in C++) is to add the qualifier static. A function that is marked static inside a class is available without having to instantiate the class. We could have a definition of the form class IO public static ... read(...) ... public static ... write(...) ... ... and then invoke these functions from outside as IO.read(...) and IO.write(...). Static functions also solve the problem of where to start—like C, we can insist that the collection of classes includes one static function with a fixed name (say, main, like C) and begin execution by invoking this method. In addition to static funtions, it also makes sense to have static data variables. This can be used to define constants, such as: class Math public static double PI = 3.1415927; public static double E = 2.7182818; public static double sin(double x) ... ... Noticethatthedesignationstaticisorthogonaltopublic/private. Doesitmakesense to have something that is private static? 14Though it appears useless at first sight, we could consider a class all of whose instances needs some “magic numbers” to do their job, but where these “magic numbers” need not be visible externally. Suppose we write a class that computes the annual interest yield on a fixed deposit. Typically, the rate varies depending on the length of the deposit, but it might be that all the variable rates are computed from a single base rate. We might then write class interest-rate private static double base_rate = 7.32; private double deposit-amount; public double threemonth-yield() ... / uses base-rate and deposit-amount / public double sixmonth-yield() ... / uses base-rate and deposit-amount / public double oneyear-yield() ... / uses base-rate / and deposit-amount / ... The idea is that all instances of interest-rate share a single copy of the static variable base_rate, so that there is no unnecessary duplication of data and also no inconsistency. Static variables are shared across all instances of a class. Thus, we can also use static variables to keep track of global information such as how many times a given function is called across all instances of the class. For example, we could write class stack private int values100; / values are stored in an array / private int tos = 0; / top of stack, initialize to 0 / private static int num_push = 0; / number of pushes across all stacks / push (int i, ...) / push i onto stack / valuestos = i; tos = tos+1; / Should check that tos 100 / num_push++; / update static variable / ... Here again, it makes sense to have num_push restricted to be private because we want it to be incremented only by the method push within the class stack. 15We have to be careful when we mix static and non-static entities in the same class. A non-static function is tied to a specific instance (object) and so can implicitly refer to the current state of the object as well as to static data (such as the yield functions above). However, since a static function is shared amongst all instances of a class (and there need not even be a single instance in existence for the static function to be used), such a function should not make any reference to any non-static components of the class. We will address this point in more detail later. 1.4 Constants (the attribute final) Consider our earlier definition class Math public static double PI = 3.1415927; ... The intention was to make the value Math.PI available everywhere to use in expressions: e.g., area = Math.PI radius radius; However, there is nothing to prevent the following improper update of the public static field PI: Math.PI = 3.25; To forbid this, we attach another attribute, indicating that a value but may not be altered. Following Java terminology, we use the notation final, so we would modify the definition above to say: class Math public static final double PI = 3.1415927; ... and achieve what we set out to have—a publicly available constant that exists without instantiating any objects. Actually, the modifier final has other ramifications. We will see these later, when we talk about inheritance. 161.5 Constructors When we create an object using new, we get an uninitialized object. We then have to set the values of the instance variables to sensible values (almost always this has to be done via appropriate methods, because these variables will be private). It is natural to ask for the analogue of a declaration of the form: int i = 10; which “creates” a variable i of type int and also, simultaneously, sets its initial state. Intheobject-orientedparadigm,thisinitializationisperformedbyspecialmethods,called constructors. A constructor is a method that is called implicitly when the object is created. It cannot be called after the object has been created. In Java, a constructor is written as a public method with no return value which takes the same name as the class. For example, we can define a class Date as follows: class Date private int day, month, year; public Date(int d, int m, int y) day = d; month = m; year = y; Now, if we write Date d = new Date(27,1,2003); d points to a new Date object whose values are initialized with day = 27, month = 1 and year = 2003. We may want to have more than one way to construct an object. If no year is supplied, we might set the field year to the current year, by default. We can add a second constructor as follows: public Date(int d, int m) day = d; month = m; year = 2003; Now, if we write 17Date d1 = new Date(27,1,2002); Date d2 = new Date(27,1); d1 calls the first constructor, as before, while d2 calls the second constructor (where year is set to 2003 by default). This ability to have multiple constructors with different “signatures” extends to methods as well. In Java, the signature of a method is its name plus the types of its arguments. One can overload method names just like we have overloaded the constructors above. In fact, in the Java built-in class Arrays, there is a static method sort that sorts arbitrary arrays of scalars. In other words, we can write: double darr = new double100; int iarr = new int500; ... Arrays.sort(darr); // sorts contents of darr Arrays.sort(iarr); // sorts contents of iarr This is done by defining, within the class Arrays, multiple methods named sort with different signatures, as follows: class Arrays ... public static void sort(double a) // sorts arrays of double ... public static void sort(int a) // sorts arrays of int ... ... When we invoke Arrays.sort with an argument a, the type of a automatically deter- mines which version of the overloaded method sort is used. Coming back to constructors, what happens if we have no constructors defined? In this case, Java provides a “default” constructor that takes no arguments and sets all instance variables to some sensible defaults (e.g., an int is set to 0). Suppose we have a class as follows: class no_constructor private int i; // some methods below ... 18We would then write something like: no_constructor n = new no_constructor(); // Note the () after // the class name However, if there is at least one constructor defined in the class, the default constructor is withdrawn. So, if we have the class Date as given above, it is an error to write: Date d = new Date(); If we want this to work, we must explicitly add a new constructor that has no arguments. Rememberthatitisnotpossibletoinvokeaconstructorlater. ThoughDate(int,int,int) is a public “method” in the class, it has a different interpretation. We cannot say; Date d = new Date(27,1,2003); ... d.Date(27,1,2003); One constructor can call another, using the word this. We can rewrite the two construc- tors in the class Date as follows: class Date private int day, month, year; public Date(int d, int m, int y) day = d; month = m; year = y; public Date(int d, int m) this(d,m,2003); The second constructor invokes the first one by supplying a fixed third argument. In Java, such an invocation using this must be the first statement in the constructor. We can reverse the constructors as follows: class Date private int day, month, year; 19public Date(int d, int m) day = d; month = m; year = 2003; public Date(int d, int m, int y) this(d,m); // this sets year to 2003 year = y; // reset year to the value supplied 20