cse 476 mobile application development
TRANSCRIPT
9/24/2020
1
CSE 476 Dennis PhillipsMobile Application Development11
Java Stuff
Things you need to know about Java
Or just might find handy…
CSE 476 Dennis PhillipsMobile Application Development22
Are you sure what this does?
int a1 = 1;int b1 = 1;
String a = "This is string " + a1;String b = "This is string " + b1;textView1.setText(a);textView2.setText(b);
if(a == b) {textView.setText("Equal");
} else {textView.setText("Not equal");
} Output:This is string 1This is string 1???
1
2
9/24/2020
2
CSE 476 Dennis PhillipsMobile Application Development33
String equality tests
if(a.equals(b)) {textView.setText("Equal");
}
a == b tests if the objects are the same, not if the contents are the same
CSE 476 Dennis PhillipsMobile Application Development44
Inheritance
Inheritance creates new classes based on classes that have already been defined. The capabilities of the base class are inherited by the derived class.
public class HatterView extends View {
@Overrideprotected void onDraw(Canvas canvas) {
super.onDraw(canvas);}
}
Here HatterViewextends View. HatterView is aView.
It’s a View and then some… Diagram
Representation
3
4
9/24/2020
3
CSE 476 Dennis PhillipsMobile Application Development55
Inheritance
We also say:
HatterView is derived from ViewView is a superclass of HatterViewView is a base class of HatterView
CSE 476 Dennis PhillipsMobile Application Development66
Polymorphism
The ability of an object of one type to appear as and be used as another type.
Lines lines = new Lines();Drawable item = lines;
The variable lines is a reference to an object of type Lines.The variable item is a reference to the same object, but only knows it as a Drawable (it just knows that much about the object)
Polymorphism applies when classes are derived from other classes
5
6
9/24/2020
4
CSE 476 Dennis PhillipsMobile Application Development77
The Key Idea of Polymorphism
An object can be known by its class or any class it is derived from.
Example:
Screen – A screen in a computer game. Can be 2D or 3D. Scene – A scene in a computer game. 3D only. Derived from Screen.CityScene – A scene set in a city. Derived from Scene.
We create an object for a city scene. We can pass that object to functions that expect a reference to a Scene or Screen.
Think of it this way: A CityScene is a Scence. A Scene is a Screen; A CityScence is a Screen;
CSE 476 Dennis PhillipsMobile Application Development88
Polymorphism in Java
Suppose we have this code:
The variable item is a reference to an object that is actually of type Lines(). That is the underlying type. We can get it back using a down cast:
Lines lines = new Lines();Drawable item = lines;
Lines linesObj = (Lines)item;
But, if item is not a reference to a Lines object, you will get a runtime exception
7
8
9/24/2020
5
CSE 476 Dennis PhillipsMobile Application Development99
android.widget.Button
CSE 476 Dennis PhillipsMobile Application Development1010
Checking if something is of the expected type
Lines linesObj = null;if(item instanceof Lines) {
linesObj = (Lines)item;}
The IDE will actually warn you sometimes if it thinks a down cast is dangerous. It’s expecting you to do a check like this.
9
10
9/24/2020
6
CSE 476 Dennis PhillipsMobile Application Development1111
Using instanceof public class Animal {}
public class Fish extends Animal {public void swim() {
// swim code…}
}
public class Bird extends Animal {public void fly() {
// fly code…}
}
for(Animal animal : animals) {if(animal instanceof Fish) {
Fish fish = (fish) animal;fish.swim();
} else if(animal instanceof Bird) {
Bird bird = (bird) animal;bird.fly();
}}
You have a collection of Animals. You can check each to determine its type and then call the appropriate function.
CSE 476 Dennis PhillipsMobile Application Development1212
Virtual Functions public class Animal {public void move() {}
}
public class Fish extends Animal {@Overridepublic void move() {
// swim code…}
}
public class Bird extends Animal {@Overridepublic void move() {
// fly code…}
}
for(Animal animal : animals) {animal.move();
}
If a base class version of a function appears in a derived class, the version in the actual underlying type will be called.
Notice how much simpler the code is in this case? Virtual functions allow us to avoid instanceof and down casts.
11
12
9/24/2020
7
CSE 476 Dennis PhillipsMobile Application Development1313
Example
private ArrayList array = new ArrayList();
for(Object obj: array) {if(obj instanceof Square) {
Square square = (Square)obj;square.draw();
} else if(obj instanceof Cube) {Cube cube = (Cube)obj;cube.displayMe();
}}
public class Square {public void draw() {
//…}
}
public class Cube {public void displayMe() {
//..}
}
CSE 476 Dennis PhillipsMobile Application Development1414
instanceof and down casts considered harmful?
Well designed object-oriented programs do not need instanceof or down casts. They can utilize virtual functions and other methods to avoid these.
Many experts consider these an indication of a bad design. However, they are pretty common in Java programming and the Android API.
hatterView = (HatterView)findViewById(R.id.hatterView);colorButton = (Button)findViewById(R.id.buttonColor);featherCheck = (CheckBox)findViewById(R.id.checkFeather);spinner = (Spinner)findViewById(R.id.spinnerHat);
Try to design so as to minimize their use
13
14
9/24/2020
8
CSE 476 Dennis PhillipsMobile Application Development1515
Why down casts become almost essential
Android creates the views for you in many cases, you only get the polymorphic View reference.
CSE 476 Dennis PhillipsMobile Application Development1616
Collaboration
public class Client {
public Client() {worker = new Worker();
worker.doSomeWork();}
}
public class Worker {
public Worker() {
}
public int doSomeWork() {// Do somethingreturn 0;
}
}I have a worker class. I ask it to do some work. In this case, how does Client know the work is done?
15
16
9/24/2020
9
CSE 476 Dennis PhillipsMobile Application Development1717
Synchronous Tasks
public class Client {
public Client() {worker = new Worker();
worker.doSomeWork();}
}
public class Worker {
public Worker() {
}
public int doSomeWork() {// Do somethingreturn 0;
}
}Worker completes all if its work before it returns. This is a synchronous task You use synchronous tasks all
of the time…
CSE 476 Dennis PhillipsMobile Application Development1818
Asynchronous Tasks
public class Client {
public Client() {worker = new Worker();
worker.doSomeWork();}
}
public class Worker {
public Worker() {
}
public void doSomeWork() {// Start doing something//...
}
private void workIsDone() {// Tell the client we are done...//...
}}
I have a worker class. I ask it to do some work. doSomeWorkimmediately returns. Worker continues to do the work and later needs to tell Client it is done. This is called an asynchronous task.
17
18
9/24/2020
10
CSE 476 Dennis PhillipsMobile Application Development1919
Asynchronous what?
How often does this happen?
Every program you have ever written in this class.
Common cases:➢ I create a GUI component and later it needs to tell me I
clicked on it.➢ I create a thread and it needs to tell me it is done
computing.➢ I create a background service and it needs to tell me a
message has arrived.
The other class may be computing in a thread or it may be computing when the GUI calls it.
CSE 476 Dennis PhillipsMobile Application Development2020
One way to do this…
public class Client {
private Worker worker;
public Client() {worker = new Worker(this);
worker.doSomeWork();}
public void workerIsDone() {
}}
public class Worker {
private Client client;
public Worker(Client client) {this.client = client;
}
public void doSomeWork() {// Start doing something//...
}
private void workIsDone() {// Tell the client we are done...client.workerIsDone();
}
}Worker knows about client, client knows about worker
19
20
9/24/2020
11
CSE 476 Dennis PhillipsMobile Application Development2121
UML
Client has a reference to the worker, so it can call functions on the worker. Worker has a reference to the client, so it can call functions on the client.
Classic bidirectional association
CSE 476 Dennis PhillipsMobile Application Development2222
But…
Worker class has turned out to be handy. Client2 wants to also use it.
public class Client2 {
private Worker worker;
public Client2() {worker = new Worker(this);
worker.doSomeWork();}
public void workerIsDone() {
}}
public class Worker {
private Client client;
public Worker(Client client) {this.client = client;
}
public void doSomeWork() {// Start doing something//...
}
private void workIsDone() {// Tell the client we are done...client.workerIsDone();
}
}
21
22
9/24/2020
12
CSE 476 Dennis PhillipsMobile Application Development2323
One solution…
public class Worker {
private Client client = null;private Client2 client2 = null;
public Worker(Client client) {this.client = client;
}
public Worker(Client2 client) {this.client2 = client;
}
public void doSomeWork() {// Start doing something//...
}
private void workIsDone() {// Tell the client we are done...if(client != null) {
client.workerIsDone();}
if(client2 != null) {client2.workerIsDone();
}}
}
CSE 476 Dennis PhillipsMobile Application Development2424
Really bad solution
public class Worker {
private Client client = null;private Client2 client2 = null;
public Worker(Client client) {this.client = client;
}
public Worker(Client2 client) {this.client2 = client;
}
public void doSomeWork() {// Start doing something//...
}
private void workIsDone() {// Tell the client we are done...if(client != null) {
client.workerIsDone();}
if(client2 != null) {client2.workerIsDone();
}}
}
Client2 was very impressed. Here comes Client3, Client4, etc.
23
24
9/24/2020
13
CSE 476 Dennis PhillipsMobile Application Development2525
Inheritance to the rescue!
CSE 476 Dennis PhillipsMobile Application Development2626
WorkerIsDone class
public class WorkerIsDone {
public void workerIsDone() {
}}
public class Worker {
private WorkerIsDone client;
public Worker(WorkerIsDone client) {this.client = client;
}
public void doSomeWork() {// Start doing something//...
}
private void workIsDone() {// Tell the client we are done...client.workerIsDone();
}
}
public class Client extends WorkerIsDone {private Worker worker;
public Client() {worker = new Worker(this); worker.doSomeWork();
}
public void workerIsDone() {
}}
25
26
9/24/2020
14
CSE 476 Dennis PhillipsMobile Application Development2727
Along comes Client2
public class WorkerIsDone {
public void workerIsDone() {
}}
public class Client2 extends WorkerIsDone {private Worker worker;
public Client2() {worker = new Worker(this); worker.doSomeWork();
}
public void workerIsDone() {
}}
public class Worker {
private WorkerIsDone client;
public Worker(WorkerIsDone client) {this.client = client;
}
public void doSomeWork() {// Start doing something//...
}
private void workIsDone() {// Tell the client we are done...client.workerIsDone();
}
}
CSE 476 Dennis PhillipsMobile Application Development2828
Happy, happy, joy, joy until…
Java (and C# and Objective-C, BTW) does not allow multiple inheritance
27
28
9/24/2020
15
CSE 476 Dennis PhillipsMobile Application Development2929
Interfaces
public interface WorkerIsDone {void workerIsDone();
}
public class Client implements WorkerIsDone {
private Worker worker;
public Client() {worker = new Worker(this);
worker.doSomeWork();}
public void workerIsDone() {
}}
An interface specifies functions that a derived class must implement!
Java allows only one base class, but as many interfaces as you like.
Only function definitionsAll assumed publicNo function bodyNo member variables
CSE 476 Dennis PhillipsMobile Application Development3030
Nested Interfacepublic class Worker {
public interface IsDone {void workerIsDone();
}
private IsDone client;
public Worker(IsDone client) {this.client = client;
}
…
public class Client implements Worker.IsDone {
private Worker worker;
public Client() {worker = new Worker(this);
worker.doSomeWork();}
public void workerIsDone() {
}}
If the interface goes with the class, nest it
You can define an interface inside a class
29
30
9/24/2020
16
CSE 476 Dennis PhillipsMobile Application Development3131
Case Study – A target view
View calls a function in the activity when you touch the target.
CSE 476 Dennis PhillipsMobile Application Development3232
TargetViewonTouchEvent()
@Overridepublic boolean onTouchEvent(MotionEvent event) {
switch(event.getActionMasked()) {case MotionEvent.ACTION_DOWN:
float x = event.getX();float y = event.getY();
// How far are we from the center?float cx = getWidth() / 2;float cy = getHeight() / 2;float dist = (float)Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)) / cx;
float score = 10 - dist * 10;
// Send the score to the calling activity???????????????????return true;
}
return super.onTouchEvent(event);}
31
32
9/24/2020
17
CSE 476 Dennis PhillipsMobile Application Development3333
If only will ever work with one activity…
public class TargetActivity extends Activity {
//…
public void onTargetHit(float score) {//...
}
}
float score = 10 - dist * 10;
// Send the score to the calling activity((TargetActivity)getContext()).onTargetHit(score);return true;
This works because Activity is derived from Context and the activity is known by the context in the View. Polymorphism…
CSE 476 Dennis PhillipsMobile Application Development3434
But, what if I want this to work with different activities?
Add this interface to TargetView
Derive TargetActivityfrom the interface
public interface Score {void onTargetHit(float score);
}
public class TargetActivityextends Activity implements TargetView.Score {
And call it this way// Send the score to the calling activity
((Score)getContext()).onTargetHit(score);
33
34
9/24/2020
18
CSE 476 Dennis PhillipsMobile Application Development3535
We have been using this…
private class ShuffleListener implements DialogInterface.OnClickListener {
From Step 3
CSE 476 Dennis PhillipsMobile Application Development3636
Nested Classes
private class Puzzle {
private class ShuffleListener implements DialogInterface.OnClickListener {
@Overridepublic void onClick(DialogInterface dialog, int which) {
shuffle();view.invalidate();
}
}
}
Classes declared inside another class.Saves creating an extra .java file.Associates the class with what it is nested in.
35
36
9/24/2020
19
CSE 476 Dennis PhillipsMobile Application Development3737
Nested Classes – Extra Java Feature
private class ShuffleListener implements DialogInterface.OnClickListener {
@Overridepublic void onClick(DialogInterface dialog, int which) {
shuffle();view.invalidate();
}
}
Nested classes declared like this have an automatic association with the enclosing class, so they can call functions and access member variables.
Declared in Puzzle
But, the allocation must be in a function in this class
CSE 476 Dennis PhillipsMobile Application Development3838
Use of a nested class
AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
ShuffleListener listener = new ShuffleListener();builder.setNegativeButton(R.string.shuffle, listener);
private class ShuffleListener implements DialogInterface.OnClickListener {
@Overridepublic void onClick(DialogInterface dialog, int which) {
shuffle();view.invalidate();
}
}
Nested Class
Where it is used
37
38
9/24/2020
20
CSE 476 Dennis PhillipsMobile Application Development3939
Anonymous classesDeclared and used in only one place and has no name
AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
builder.setNegativeButton(R.string.shuffle, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {
shuffle();view.invalidate();
}});
private class ShuffleListener implements DialogInterface.OnClickListener {@Overridepublic void onClick(DialogInterface dialog, int which) {
shuffle();view.invalidate();
}}
Original Nested Class
Anonymous Nested Class
Worksheet
CSE 476 Dennis PhillipsMobile Application Development4040
Problem 1
public class GameActivity extends Activity implements HourGlassView.HourGlass {
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);setContentView(R.layout.activity_game);
HourGlassView hourGlassView = (HourGlassView)findViewById(R.id.hourGlass);
hourGlassView.setClient(this);}
@Overridepublic void turned() {}
@Overridepublic void expired() {}
}
public class HourGlassView extends View {
public interface HourGlass {void turned();void expired();
}//…
}
39
40
9/24/2020
21
CSE 476 Dennis PhillipsMobile Application Development4141
Problem 2public class GameActivity extends Activity {
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);setContentView(R.layout.activity_game);
HourGlassView hourGlassView = (HourGlassView)findViewById(R.id.hourGlass);hourGlassView.setClient(new HourGlassCallback());
}
private class HourGlassCallback implements HourGlassView.HourGlass {@Overridepublic void turned() {}@Overridepublic void expired() {}
}}
CSE 476 Dennis PhillipsMobile Application Development4242
Problem 3public class GameActivity extends Activity {
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);setContentView(R.layout.activity_game);
HourGlassView hourGlassView = (HourGlassView)findViewById(R.id.hourGlass);hourGlassView.setClient(new HourGlassView.HourGlass() {
@Overridepublic void turned() {}
@Overridepublic void expired() {}
});}
}
41
42
9/24/2020
22
CSE 476 Dennis PhillipsMobile Application Development4343
Named to Anonymous
hourGlassView.setClient(new HourGlassCallback());
private class HourGlassCallback implements HourGlassView.HourGlass {public void turned() {}
public void expired() {}
}
hourGlassView.setClient(new HourGlassView.HourGlass() {public void turned() {}
public void expired() {}
});
CSE 476 Dennis PhillipsMobile Application Development4444
Static Nested Classes
private static class Parameters {
public String imagePath = null;public int hat = HAT_BLACK;
}
Static nested classes work like C++ nested classes. They do not have the association with the enclosing object. You can’t call member functions or access variables outside the nested class itself
They cost less than regular nested classes if you don’t need that access. You can instantiate them anywhere they are visible. And, they make some other things possible…
43
44
9/24/2020
23
CSE 476 Dennis PhillipsMobile Application Development4545
Static Members
public static final int HAT_BLACK = 0;
private static Parameters params = new Parameters();
Static members are associated with the class, not an object of the class
params acts like a global variable. It is created when the program starts and it stays around. Turns out to be useful in Android in many cases…
What if I exit this activity and return later on?
The value of static variables will persist as long
as the class is loaded, strange behavior …
CSE 476 Dennis PhillipsMobile Application Development4646
Static vs. normal member functions
public class Vector3 {
public final float [] v = new float[3];
public static float length(Vector3 vector) {float [] v = vector.v;return (float)Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
public float length() {return (float)Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
public static float dot(Vector3 a, Vector3 b) {return (float)Math.sqrt(a.v[0] * b.v[0] + a.v[1] * b.v[1] + a.v[2] * b.v[2]);
}}
45
46
9/24/2020
24
CSE 476 Dennis PhillipsMobile Application Development4747
Serialization
Object Serialization
Deserialization Object
Byte Stream
Byte Stream
Serialization converts a Java object into a stream of bytes. Deserialization reverses the process
CSE 476 Dennis PhillipsMobile Application Development4848
What does it take?
private static class Parameters implements Serializable {private static final long serialVersionUID = -2846716033219475583L;public String imagePath = null;public int hat = HAT_BLACK;
}
But, every class Parameters is derived from must be serializable.
Every member of Parameters must be serializable (with one exception).
You are supposed to provide a variable serialVersionUID with an integer value. Some IDE’s will generate one for you.
“If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class”. Java docs
47
48
9/24/2020
25
CSE 476 Dennis PhillipsMobile Application Development4949
Using this…public void putToBundle(String key, Bundle bundle) {
bundle.putSerializable(key, params);}
public void getFromBundle(String key, Bundle bundle) {params = (Parameters)bundle.getSerializable(key);
// Ensure the options are all setsetColor(params.color);setImagePath(params.imagePath);setHat(params.hat);
}
CSE 476 Dennis PhillipsMobile Application Development5050
transient
Declare a member variable transient if you don’t want it serialized…
private static class Parameters implements Serializable {public String imagePath = null;public int hat = HAT_BLACK;public transient Bitmap imageBmp = null;
}
There are often members that represent state and members that are temporary, derived, loaded, or not serializable at all.
49
50
9/24/2020
26
CSE 476 Dennis PhillipsMobile Application Development5151
Serialization is supposed to be really slow
I’m seeing numerous reports online that serialization is very slow and should never be used.
CSE 476 Dennis PhillipsMobile Application Development5252
Serialization is supposed to be really slow
I’m seeing numerous reports online that serialization is very slow and should never be used.
Note that for a benchmark of 10,000 values, very slow was 0.3 seconds… Often you are moving 10 values…
But, use only for small amounts of data…
51
52
9/24/2020
27
CSE 476 Dennis PhillipsMobile Application Development5353
Moving data around in Android
Save into bundles and intentsSerialize into bundles and intentsStatic variablesParcelable – You implement serialization yourself
Parcelable is the recommended solution for larger objects, but some report it slower than serializable
CSE 476 Dennis PhillipsMobile Application Development5454
Using static member variables to share data
Suppose you have a large data structure that is passed between activities.
Serialization/deserialization, no matter how you do it, can be costly and result in redundant copies in memory.
53
54
9/24/2020
28
CSE 476 Dennis PhillipsMobile Application Development5555
Using static member variables to share data
public class SomeActivity extends Activity {
private static ApplicationData data = new ApplicationData();
public static ApplicationData getApplicationData() {return data;
}
//…
You can also allocate the object in the constructor, so it does not exist until you need it. Use sparingly: you are consuming memory as long as your application exists.
In some other activity:
ApplicationData data = SomeActivity.getApplicationData();
If you need to do this much, look at extending the Application class. It has functions that will indicate if memory is getting low.
CSE 476 Dennis PhillipsMobile Application Development5656
Another approach…
public class SomeActivity extends Activity {
private static ApplicationData data = null;
public static ApplicationData getApplicationData() {if(data == null) {
data = new ApplicationData();}return data;
}
//…
55
56
9/24/2020
29
CSE 476 Dennis PhillipsMobile Application Development5757
Singleton Patternpublic class ServerComm {
// Singleton pattern objectprivate static final ServerComm server = new ServerComm();
// Private constructor prevents other classes from// creating a new object of this typeprivate ServerComm() {}
// Get a reference to the singleton server objectpublic static ServerComm get() {
return server;} Exactly one of this object will always exist.
You can get a reference to it from anywhere this way:
ServerComm serverComm = ServerComm.get();
This is referred to as “eager initialization” since the object is created when the program starts…
CSE 476 Dennis PhillipsMobile Application Development5858
Singleton PatternLazy initialization
public class ServerComm {
// Singleton objectprivate static ServerComm server = null;
// Private constructor prevents other classes from// creating a new object of this typeprivate ServerComm() {}
// Get a reference to the singleton server objectpublic static ServerComm get() {
if(server == null) {server = new ServerComm();
}return server;
}
Maybe you don’t know that you’ll use this object, or you want to make the program start faster?
57
58
9/24/2020
30
CSE 476 Dennis PhillipsMobile Application Development5959
Exceptions
Some operations can fail and cause Exceptions:
EditText num = (EditText)findViewById(R.id.temperature);float x = Float.parseFloat(num.getText().toString());
What if I enter the wrong value?
General rule of programming: Assume all input is evil!
CSE 476 Dennis PhillipsMobile Application Development6060
NumberFormatException
02-02 16:24:48.986: E/AndroidRuntime(10354): java.lang.RuntimeException: Unable to start activity: java.lang.NumberFormatException: Invalid float: “nahnah"
Exceptions are the standard way to capture errors in Java. Most failures are indicated by exceptions.
59
60
9/24/2020
31
CSE 476 Dennis PhillipsMobile Application Development6161
try/catchEditText editText = (EditText) dlg
.findViewById(R.id.dlgFloatOne);float x;try {
x = Float.parseFloat(editText.getText().toString());
} catch (NumberFormatException ex) {
// Value is badeditText.requestFocus();editText.selectAll();return;
}
What you put in the try block will be tried. If it throws an exception, control moves immediately to the catch block
CSE 476 Dennis PhillipsMobile Application Development6262
Multiple catch blocks
try {// Parse the URLURL url = new URL(COMMUNITY_URL);
// Create a new HTTP connection// and get a streamHttpURLConnection connection = (HttpURLConnection)url.openConnection();int responseCode = connection.getResponseCode();if(responseCode == HttpURLConnection.HTTP_OK) {
stream = connection.getInputStream();}
} catch(MalformedURLException ex) {return;
} catch(IOException ex) {return;
}
If more than one exception is possible, create a catch for each.
Some exceptions MUST be handled. You have to have a try around them.
61
62
9/24/2020
32
CSE 476 Dennis PhillipsMobile Application Development6363
Mutable and immutable
A class is said to be mutable if the state can be changed after creation.
Otherwise, it is immutable.
String is immutable. You cannot change the value of a string.
So, what is this code doing:
String s1 = “I’m okay”;s1 = s1 + “, you’re okay”;
CSE 476 Dennis PhillipsMobile Application Development6464
Example
String s1 = “I’m okay”; “I’m okay”
“I’m okay, you’re okay”
String s2 = s1;
s1 = s1 + “, you’re okay”;
s1
s2
“I’m okay”s1
“I’m okay”s2
s1
Whenever the string changes, a new object is created with the new string in it
63
64