0% found this document useful (0 votes)
18 views6 pages

Prelab 4

The document covers binary operations and type conversions in Java, detailing how operands are converted to a common type during operations. It explains legal primitive-type conversions, the difference between widening and narrowing conversions, and the use of wrapper types for primitives. Additionally, it discusses the limitations of casting between unrelated reference types and the application of wrapper types in generic collections like ArrayList.

Uploaded by

adamdiab117
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
18 views6 pages

Prelab 4

The document covers binary operations and type conversions in Java, detailing how operands are converted to a common type during operations. It explains legal primitive-type conversions, the difference between widening and narrowing conversions, and the use of wrapper types for primitives. Additionally, it discusses the limitations of casting between unrelated reference types and the application of wrapper types in generic collections like ArrayList.

Uploaded by

adamdiab117
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 6

CSC245B Lab 4 – Prelab Reading

Spring 2025

1 Binary Operations
In Java, when two values are combined with a binary operator (such as n + f), both operands are converted
to a common type before the operation is carried out:
• If either of the operands is of type double, the other one will be converted to a double.
• Otherwise, if either of the operands is of type float, the other one will be converted to a float.
• Otherwise, if either of the operands is of type long, the other one will be converted to a long.
• Otherwise, both operands will be converted to an int.

2 Legal Primitive-type Conversions in Java


Figure 1 summarizes the main legal primitive type conversions in Java.

Figure 1: Primitive types legal conversions

The solid arrows in Figure 1 denote conversions without information loss. The dotted arrows, on the
other hand, denote conversions that may lose precision.

1
Example conversion with precision loss: a large integer such as 123456789 has more digits than the
float type can represent. When the integer is converted to a float, the resulting value has the correct
magnitude but loses some precision.
1 int n = 123456789;
2 float f = n; // f is 1.23456792 E8

Example without precision loss: Java allows assigning an int to a double as shown below, and such a
conversion does not lead to any precision loss.
1 int n = 13;
2 double d = n; // implicit conversion ; no need for explicit casting

The opposite is not true! Assigning a double to an int is illegal:


1 double d = 13.0;
2 int n = d; // illegal ( compilation error )

Explicit casting is required to correct the error above:


1 double d = 13.0;
2 int n = (int) d; // casting needed

3 Primitive Type Conversions


As mentioned, assigning a double to an int is not allowed in Java without explicit casting, but we can
assign an int to a double without this worry (no need for casting.) In general, “widening conversions”
DO NOT need casting, whereas “narrowing conversions” DO need casting.

The primitive types from the “narrowest” to the “widest”:


byte < short < char < int < long < float < double

Note that the boolean type cannot be converted into another type (not even with casting)

4 Reference Type Conversions


The general rule stated above for type conversions also applies to reference types; that is, a widening
conversion does not require explicit casting while a narrowing conversion does. But how can we tell if a
conversion is legal and if it is a narrowing or widening conversion for reference types?
Before we answer this question, we need to learn about Java’s interfaces. An interface is a collection
of constants and abstract methods (similar to how a class is a collection of methods and fields). By
implementing an interface, a class inherits the members of the interface (more on inheritance, abstract
classes, and interfaces later in the course).
A widening conversion occurs when a type T is converted into a “wider” type U (U ← T ). The following
are common cases of widening conversions:

2
• T and U are class types, and U is a superclass of T (i.e., class T extends class U )
• T and U are interface types, and U is a super-interface of T (i.e., interface T extends interface U )
• T is a class that implements interface U
The “widest” type is java.lang.Object.

Widening conversions do not require an explicit cast. For example, assume that the class Musician
extends (i.e., inherits from) the class Person, then the following conversions are possible:
1 Musician m = new Musician ();
2 Person p = m; // widening conversion (no explicit casting needed )
3 Musician m2 = ( Musician ) p; // narrowing conversion that requires explicit
casting

Note, however, that while the below conversion is accepted at compile time, it will fail at
runtime:
1 Person p = new Person ();
2 // Musician m = p; // compiler error : narrowing conversion , requires explicit
casting
3 Musician m = ( Musician ) p; // explicit casting removes the compilation error
but will fail at runtime ( ClassCastException )

Below is a further explanation in the context of Problem 2 of Lab 4:

In PersonDemo.java of Lab 4, you will loop over a list of Person objects. Some of these persons are
Musicians, and others are not. When you downcast the ith person to a Musician (Musician musician
= (Musician) person;), the casting will only be successful at runtime if the current person is actually
a musician. Particularly, the casting will not work for maya, who was instantiated as a Person, nor
for bahaa, who was instantiated as a Student. The casting will be successful only for the iterations
corresponding to dave and clara, who were instantiated as Musician objects.
From the compiler’s perspective, all these objects are of type Person, which is the declared type of
the list; that is, the compiler is unaware of the runtime types, which are the instantiation types (what
comes after the new keyword), because new is a runtime operation. Therefore, the compiler allows the
conversion in all the iterations, although not all of them will be successful at runtime. This is why in
PersonDemo.java, an instanceof check is used before downcasting so that the casting is safe and is
guaranteed to work at runtime. instanceof is a runtime check that verifies the runtime type of the
object.

5 Wrappers for the Primitive Types


Each of the primitive types in Java has a corresponding wrapper reference type (think of it as the class-view
of the type). The goal is to treat primitives as objects, which is needed in some scenarios (ArrayList
example below). However, if not needed, it is generally recommended to avoid using the wrapper types
because primitives are more efficient. Table 1 shows all the wrappers for primitive types.
To convert between a wrapper and its corresponding primitive, we can use the accessor methods shown
in the table, such as intValue() for Integer or doubleValue() for Double. However, Java also provides

3
autoboxing and unboxing, where it automatically converts between primitives and their wrappers when
necessary. For example, in the statement Integer num = 10;, Java autoboxes the primitive 10 into an
Integer object. Similarly, in int x = num;, Java unboxes num back into an int. These conversions
happen behind the scenes using the appropriate methods (the ones shown in the table in addition to other
more efficient methods such as Integer.valueOf()).

Class Constructor Accessor Method


Byte public Byte(byte) public byte byteValue()
Short public Short(short) public short shortValue()
Integer public Integer(int) public int intValue()
Long public Long(long) public long longValue()
Float public Float(float) public float floatValue()
Double public Double(double) public double doubleValue()
Character public Character(char) public char charValue()
Boolean public Boolean(boolean) public boolean booleanValue()

Table 1: Wrapper classes, constructors, and cccessor methods

More on Integer.valueOf(): As mentioned above, you can assign a primitive value to its corresponding
wrapper type in Java. For example, you can write Integer x = 3; rather than Integer x = new Integer(3);
When we write, Integer x = 3; there is no conversion or casting happening (it is not possible to cast
a primitive to a reference type), but Java is calling Integer.valueOf(3) behind the scenes. This is called
(autoboxing) where the primitive type is auto-boxed into its wrapper type. But how does Integer.valueOf(3)
work? See the brain teaser at the end of this document.

4
6 No Casting between Unrelated Reference Types
Conversions or casting between unrelated reference types (with no inheritance relationship) is not possible.
For example, while we can convert the primitive int to a double or vice versa via casting, we cannot
directly convert nor cast the wrapper Integer to Double or vice versa. This is because Double is not a
super-class of Integer. If such a conversion is needed, we generally use special methods that do a similar
function (e.g., the valueOf method):
1 Integer x = 3;
2 Double d = Double . valueOf (x);

7 Application of Wrapper Types: Type Parameters for ArrayList


An ArrayList in Java is a dynamic list data structure (similar to Python’s list). The code snippet below
shows an example usage of ArrayList (requires importing java.util.ArrayList). As shown, when we
create our list, we need to specify the type of elements in the list between angle brackets – <Integer> in
the given example. This means that all the elements added to the list should be integers. Here, using the
primitive <int> is not allowed. ArrayList is an example of a generic type. A generic type in Java is a
class or interface that can operate on objects of various types while providing compile-time type safety; you
will study generics in more detail later in the course; for now, you just need to know that in the context
of generic type parameters (<Integer> in the example below), primitive types are not allowed; you need
to use the wrappers instead of primitives.
1 ArrayList < Integer > myList = new ArrayList < Integer >() ;
2

3 myList . add (4) ; // The primitive 4 is autoboxed to the Integer type


4 myList . add (11) ;
5 myList . add (20) ;
6 myList . add (9) ;
7 myList . add (17) ;
8

9 for(int i =0; i < myList . size (); i ++) {


10 System . out . print ( myList . get (i) + " ");
11 }

5
8 Brain Teaser
Consider the codes in Figure 2-a, and try to make sense of the outputs displayed. In particular, try to
understand why the output highlighted in red is not consistent with the output of the previous example
(although the scenario is exactly the same except for using the value 3 instead of 300!) Need a hint?
Check the documentation of Integer valueOf method (Figure 2-b).
dummy

Figure 2 - a Figure 2 - b

You might also like