ArrayList_Serializable Demo Fails

In trying to learn about Serializable I found this example online: https://samderlust.com/dev-blog/java/write-read-arraylist-object-file-java but am unable to get it to run without error. Similar efforts with other examples as well as my own project have failed. Can anyone get this demo to run without error in our IDE? Thanks.

import java.io.*;
import java.util.ArrayList;

class Person implements Serializable {
  String firstName;
  String lastName;
  int birthYear;

   Person(String firstName, String lastName, int birthYear) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.birthYear = birthYear;
  }

  @Override
    public String toString() {
    return "Person{" +
      "firstName='" + firstName + '\'' +
      ", lastName='" + lastName + '\'' +
      ", birthYear=" + birthYear +
      "}\n";
  }
}

void setup() {
  Person p1 = new Person("Jony", "Deep", 1980);
  Person p2 = new Person("Andrew", "Justin", 1990);
  Person p3 = new Person("Valak", "Susan", 1995);

  ArrayList<Person> people = new ArrayList<>();

  people.add(p1);
  people.add(p2);
  people.add(p3);

  //write to file - change filePath for your system
  try {
    FileOutputStream writeData = new FileOutputStream("/Users/xxxx/peopledata.ser");
    ObjectOutputStream writeStream = new ObjectOutputStream(writeData);

    writeStream.writeObject(people);
    writeStream.flush();
    writeStream.close();
  }
  catch (IOException e) {
    e.printStackTrace();
  }
  // Change filePath to match one above 
  try {
    FileInputStream readData = new FileInputStream("/Users/xxxx/peopledata.ser");
    ObjectInputStream readStream = new ObjectInputStream(readData);

    ArrayList people2 = (ArrayList<Person>) readStream.readObject();
    readStream.close();

    println(people2.toString());
  }
  catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
  }
}
1 Like

Hi @svan,

this can’t work that way in processing context. Remember that your class defined here is in the end a non static inner class of a class extends PApplet (which itself does not implement Serializable). As a consequence the inner class (here People) is not serializable.

Read the reason why here:
https://docs.oracle.com/en/java/javase/17/docs/specs/serialization/serial-arch.html#the-serializable-interface

You can bypass this circumstance by moving the People class into a file People.java (Add Tab) and adding a public before the constructor of it:

People.java:

import java.io.*;

class Person implements Serializable {
  String firstName;
  String lastName;
  int birthYear;

   public Person(String firstName, String lastName, int birthYear) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.birthYear = birthYear;
  }

  @Override
    public String toString() {
    return "Person{" +
      "firstName='" + firstName + '\'' +
      ", lastName='" + lastName + '\'' +
      ", birthYear=" + birthYear +
      "}\n";
  }
}

Cheers
— mnse

2 Likes
  • Although it’s commendable, it isn’t required at all! :smirk:
  • We can still keep the class inside a “.pde” file by simply declaring it as static. :bulb:
  • Doing so we decouple the nested class from the sketch’s PApplet subclass. :broken_heart:
  • As a clear example, take a look at this old post about Serializable: :eyeglasses:

BtW, I’ve updated that previous old example to use a working Wikipedia image URL now: :link:


“Serializable_PImage.pde”:

/**
 * Serializable PImage (v1.1.3)
 * GoToLoop (2019/Dec/26)
 *
 * https://Discourse.Processing.org/t/arraylist-serializable-demo-fails/42770/3
 *
 * https://Discourse.Processing.org/t/
 * deserializing-objects-sent-from-a-client-program-to-a-server-program/16124/8
 */

static final String
  SERIALFILE = "imagen.serial", 

  FILENAME = "Wikipedia-logo-v2.png", 

  URL = "https://" + "www.Wikipedia.org/" +
  "portal/wikipedia.org/assets/img/" + FILENAME;

PImage img;

void settings() {
  img = loadAndSaveRemoteImage(URL);
  if (img == null) img = createImage(100, 100, RGB);

  size(img.width, img.height);
  noLoop();
}

void setup() {
  saveSerial(SERIALFILE, new SerializableImage(img));

  final SerializableImage sImg = (SerializableImage) loadSerial(SERIALFILE);
  println(sImg, sImg == sImg.clone());
  
  img = sImg.recreateImage();
}

void draw() {
  background(img);
}

“Functions.pde”:

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectOutput;

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectInput;

PImage loadAndSaveRemoteImage(final String url) {
  final File f = dataFile(new File(url).getName());
  final String path = f.getPath();
  final PImage img = loadImage(f.isFile() ? path : url);

  println(url + ENTER + path);
  if (img != null) img.save(path);

  return img;
}

void saveSerial(final String filename, final Object o) {
  final File f = dataFile(filename);
  createPath(f);

  try {
    final ObjectOutput out = new ObjectOutputStream(new FileOutputStream(f));
    out.writeObject(o);
    out.close();
  }

  catch (final IOException e) {
    e.printStackTrace();
  }
}

Object loadSerial(final String filename) {
  final File f = dataFile(filename);
  println(f);

  try {
    final ObjectInput in = new ObjectInputStream(new FileInputStream(f));
    final Object o = in.readObject();
    in.close();
    return o;
  }

  catch (final IOException e) {
    e.printStackTrace();
  }

  catch (final ClassNotFoundException e) {
    e.printStackTrace();
  }

  return null;
}

“SerialClass.pde”:

import java.io.Serializable;

static public class SerializableImage implements Cloneable, Serializable {
  static protected final long serialVersionUID = 1_620_140_667;

  public int w, h, type, scale, pix[];

  public SerializableImage(final PImage img) {
    w = img.width;
    h = img.height;

    type = img.format;
    scale = img.pixelDensity;

    img.loadPixels();
    pix = img.pixels.clone();
  }

  public PImage recreateImage() {
    return recreateImage(null);
  }

  public PImage recreateImage(final PApplet p) {
    final PImage img = new PImage(w, h, type, scale);
    img.parent = p;

    img.loadPixels();
    PApplet.arrayCopy(pix, img.pixels);
    img.updatePixels();

    return img;
  }

  @Override public SerializableImage clone() {
    try {
      final SerializableImage sImg = (SerializableImage) super.clone();
      sImg.pix = sImg.pix.clone();
      return sImg;
    }

    catch (final CloneNotSupportedException e) {
      throw new RuntimeException(e);
    }
  }

  @Override public String toString() {
    return "[ " + w + " x " + h + " ]: " + w * h;
  }
}

Or if you still prefer a separate tab file “.java” approach: :coffee:

“SerializableImage.java”:

package processing.core;

public class SerializableImage implements Cloneable, java.io.Serializable {
  static protected final long serialVersionUID = 1_620_140_667;

  public int w, h, type, scale, pix[];

  public SerializableImage(final PImage img) {
    w = img.width;
    h = img.height;

    type = img.format;
    scale = img.pixelDensity;

    img.loadPixels();
    pix = img.pixels.clone();
  }

  public PImage recreateImage() {
    return recreateImage(null);
  }

  public PImage recreateImage(final PApplet p) {
    final PImage img = new PImage(w, h, type, scale);
    img.parent = p;

    img.loadPixels();
    PApplet.arrayCopy(pix, img.pixels);
    img.updatePixels();

    return img;
  }

  @Override public SerializableImage clone() {
    try {
      final SerializableImage sImg = (SerializableImage) super.clone();
      sImg.pix = sImg.pix.clone();
      return sImg;
    }

    catch (final CloneNotSupportedException e) {
      throw new RuntimeException(e);
    }
  }

  @Override public String toString() {
    return "[ " + w + " x " + h + " ]: " + w * h;
  }
}
3 Likes

Adding “static public” to the class does indeed fix the demo. Thanks.

1 Like

However, when I try to apply this technique to my own class, it fails because Processing won’t let me mix static and non-static:

import java.io.*;
import java.util.ArrayList;

ArrayList<Rect> rects;

static public class Rect implements Serializable {

  int x, y, w, h;
  color bkgrnd;

  // Constructor
  public Rect(int xpos, int ypos, int wide, int ht, color background) {
    this.x = xpos;
    this.y = ypos;
    this.w = wide;
    this.h = ht;
    this.bkgrnd = (color)background;
  }

// This causes error
  void display() {
    fill(bkgrnd);
    rect(x, y, w, h);
  }
}

void setup() {
  size(600, 600);
  rects = new ArrayList<Rect>();
  rects.add(new Rect(30, 30, 150, 50, color(0, 255, 0)));
  
  // Change filePath for your system
  try {
    FileOutputStream writeData = new FileOutputStream("/Users/xxxxx/rects.txt");
    ObjectOutputStream writeStream = new ObjectOutputStream(writeData);

    writeStream.writeObject(rects);
    writeStream.flush();
    writeStream.close();
  }
  catch (IOException e) {
    e.printStackTrace();
  }
  // Change filePath to match one above 
  try {
    FileInputStream readData = new FileInputStream("/Users/xxxxx/rects.txt");
    ObjectInputStream readStream = new ObjectInputStream(readData);

    ArrayList rects2 = (ArrayList<Rect>) readStream.readObject();
    readStream.close();

    println(rects2.toString());
  }
  catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
  }
}

Both fill() & rect() are instance (non-static) methods from class PApplet.

In my own SerializableImage::recreateImage() method, it requests an instance of PApplet as a parameter named p, so it can be assigned to PImage::parent field:

You can do the same for your Rect::display() method:

void display(final PApplet p) {
  p.fill(bkgrnd);
  p.rect(x, y, w, h);
}

You can also request a PGraphics instance as well, so it’s even more flexible:

void display(final PApplet p) {
  display(p.getGraphics());
}

void display(final PGraphics pg) {
  pg.fill(bkgrnd);
  pg.rect(x, y, w, h);
}

That works, but how do I call it? The following causes a null pointer exception:

PApplet p;
// How do I get an instance of PApplet?
void draw(){
  for (int i = 0; i < rects.size(); i++) {
    Rect r = rects.get(i);
    r.display(p);
  }
}

Keyword this in any top-level function inside a “.pde” file refers to a subclass PApplet instance:

void draw() { // PApplet top-level method
  for (final Rect r : rects) r.display(this); // `this` is subtype PApplet here
}
void draw() { // PApplet top-level method
  for (final Rect r : rects) r.display(g); // PApplet::g is of subtype PGraphics
}

I should have known that, but didn’t try it. Thanks.

The following source code demonstrates using a Rect class with an ArrayList for drawing rectangles and Serializable for saving/opening a rectangle file (set filePath for your system). A menuBar class is used to add generic buttons to the demo. The Rect class may be placed under a Tab ‘as is’ and will remain functional. Thanks to @GoToLoop for making this possible with Serializable.

import java.io.*;

MenuBar menuBar;
ArrayList<Rect> rects;
int selectedRect;

class MenuBar {
  int x, y, w, h;
  color bkgrnd;

  MenuBar(int xpos, int ypos, int wide, int ht, color background) {
    x = xpos;
    y = ypos;
    w = wide;
    h = ht;
    bkgrnd = (color)background;
  }

  void button(int x, int y, int w, int h, String title) {
    fill(255);
    rect(x, y, w, h, 15);
    fill(0);
    textSize(18);
    textAlign(CENTER);
    text(title, x, y, w, h);
  }

  void display() {
    noStroke();
    fill(bkgrnd);
    rect(x, y, w, h);
    stroke(0);
    button(30, 15, 110, 24, "Add Rect");
    button(150, 15, 80, 24, "Save");
    button(240, 15, 80, 24, "Clear");
    button(330, 15, 80, 24, "Open");
    button(420, 15, 130, 24, "Remove Rect");
  }
}

static public class Rect implements Serializable {

  float x, y, w, h;
  color bkgrnd;
  boolean selected;

  // Constructor
  public Rect(float xpos, float ypos, float wide, float ht, color background) {
    this.x = xpos;
    this.y = ypos;
    this.w = wide;
    this.h = ht;
    this.bkgrnd = (color)background;
  }

  void display(PApplet p) {
    p.fill(bkgrnd);
    p.rect(x, y, w, h);
  }
}

void saveArrayList() {
  try {
    FileOutputStream writeData = new FileOutputStream("/Users/xxxxx/rects.txt");
    ObjectOutputStream writeStream = new ObjectOutputStream(writeData);
    writeStream.writeObject(rects);
    writeStream.flush();
    writeStream.close();
  }
  catch (IOException e) {
    e.printStackTrace();
  }
}

void openArrayList() {
  try {
    FileInputStream readData = new FileInputStream("/Users/xxxxx/rects.txt");
    ObjectInputStream readStream = new ObjectInputStream(readData);
    rects = (ArrayList<Rect>) readStream.readObject();
    readStream.close();

    for (int i = 0; i < rects.size(); i++) {
      Rect r = rects.get(i);
      println("[" + i + "]" + r.x + "," + r.y + "," +  r.w + ","  + r.h + "," + red(r.bkgrnd) + "," + green(r.bkgrnd) + "," + blue(r.bkgrnd));
    }
  }
  catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
  }
}

void addRect() {
  color bkgrnd = color(random(255), random(255), random(255));
  rects.add(new Rect(random(300), random(60, 400), random(300), random(30, 100), bkgrnd));
}

void removeRect() {
  rects.remove(selectedRect);
}

void clearArrayList() {
  for (int i = rects.size() - 1; i >= 0; i--) {
    rects.get(i);
    rects.remove(i);
  }
}

void setup() {
  size(600, 600);
  rects = new ArrayList<Rect>();
  menuBar = new MenuBar(0, 0, width, 60, color(180));
}

void draw() {
  background(209);
  menuBar.display();
  for (int i = 0; i < rects.size(); i++) {
    Rect r = rects.get(i);
    r.display(this);
    if (r.selected) {
      fill(255);
      rect(r.x+r.w-5, r.y+r.h-5, 10, 10);
    }
  }
}

void mousePressed() {
  if ((mouseX >= 30) && (mouseX <= 30 + 110) && (mouseY >= 15) && (mouseY <= 15 + 24)) {
    addRect();
  }
  if ((mouseX >= 150) && (mouseX <= 150 + 80) && (mouseY >= 15) && (mouseY <= 15 + 24)) {
    println("Save btn hit.");
    saveArrayList();
  }
  if ((mouseX >= 240) && (mouseX <= 240 + 80) && (mouseY >= 15) && (mouseY <= 15 + 24)) {
    clearArrayList();
  }
  if ((mouseX >= 330) && (mouseX <= 330 + 80) && (mouseY >= 15) && (mouseY <= 15 + 24)) {
    openArrayList();
  }
  if ((mouseX >= 420) && (mouseX <= 420 + 130) && (mouseY >= 15) && (mouseY <= 15 + 24)) {
    removeRect();
  }

  for (int i = 0; i < rects.size(); i++) {
    Rect r = rects.get(i);
    if ((mouseX >= r.x) && (mouseX <= r.x + r.w) && (mouseY >= r.y) && (mouseY <= r.y + r.h)) {
      r.selected = true;
      selectedRect = i;
      println("selected Rect = ", selectedRect);
    } else {
      r.selected = false;
    }
  }
}

1 Like