Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Technical Tips

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
Print Button
 
Tech Tips archive

Tech Tips
August 15, 2000

WELCOME to the Java Developer ConnectionSM (JDC) Tech Tips, August 15, 2000. This issue covers:

This issue of the JDC Tech Tips is written by Glen McCluskey.

These tips were developed using JavaTM 2 SDK, Standard Edition, v 1.3.


MANIPULATING JAVA ARRAYS

If you've done much Java programming, you're probably familiar with the use of Java collections such as Vector and ArrayList. For example, you can create a collection and add an element to it by saying:

    List lst = new ArrayList();

    lst.add(new Integer(37));

In this particular example, an integer value 37 is used to construct an Integer wrapper object, which is then added to the list.

This simple example illustrates a fundamental point about collections -- they are used to manipulate lists of objects, where each object is of a class or interface type. So, for example, an ArrayList can be composed of objects of types such as Object, String, Float, and Runnable. Collections do not work on lists of primitive type, such as an array of integers.

If you're using primitive arrays in your program, how can you manipulate them? This tip illustrates several techniques you can use.

The first technique is sorting. The java.util.Arrays class contains a set of class methods for sorting and searching arrays. For example, you can say:

    import java.util.Arrays;

    public class ArrayDemo1 {
        public static void main(String args[]) {
            int vec[] = {37, 47, 23, -5, 19, 56};
            Arrays.sort(vec);
            for (int i = 0; i < vec.length; i++) {
                System.out.println(vec[i]);
            }
        }
    }

The demo initializes an array of integers, and then calls Arrays.sort to sort the array in ascending order.

In a similar way, you can do binary searching on a sorted array:

    import java.util.Arrays;

    public class ArrayDemo2 {
        public static void main(String args[]) {
            int vec[] = {-5, 19, 23, 37, 47, 56};
            int slot = Arrays.binarySearch(vec, 35);
            slot = -(slot + 1);
            System.out.println("insertion point = " + slot);
        }
    }

There's one tricky aspect of this particular program. If the binary search fails to find the requested element in the array, it returns:

    -(insertion point) - 1

The demo program calls the search method with an argument of 35, which is not in the array. The method returns a value of -4. If the value -4 is incremented and then negated, the result is 3; this is the point at which 35 would be inserted into the array. In other words, the values -5, 19, and 23 occupy locations 0, 1, and 2 in the array. The value 35 therefore belongs at index 3, with the values 37, 47, and 56 pushed down. The search method does not actually insert 35 into the array, but just indicates where it should go.

Beyond sorting and searching, what else can you do with primitive arrays? Another useful technique is to take an array of primitive type and turn it into an equivalent Object array. Here each corresponding element is a wrapped version of the primitive element. So, for example, a value 37 in the primitive array becomes Integer(37) in the wrapped array.

Here's how you can do this:

    import java.util.Arrays;
    import java.lang.reflect.Array;
    
    public class ArrayDemo3 {
    
        // if input is a single-dimension primitive array,
        // return a new array consisting of wrapped elements,
        // else just return input argument
    
        public static Object toArray(Object vec) {
    
            // if null, return
    
            if (vec == null) {
                return vec;
            }
    
            // if not an array or elements not primitive, return
    
            Class cls = vec.getClass();
            if (!cls.isArray()) {
                return vec;
            }
            if (!cls.getComponentType().isPrimitive()) {
                return vec;
            }
    
            // get array length and create Object output array
    
            int length = Array.getLength(vec);
            Object newvec[] = new Object[length];
    
            // wrap and copy elements
    
            for (int i = 0; i < length; i++) {
                newvec[i] = Array.get(vec, i);
            }
    
            return newvec;
        }
    
        public static void main(String args[]) {
    
            // create a primitive array
    
            int vec[] = new int[]{1, 2, 3};
    
            // wrap it
    
            Object wrappedvec[] = (Object[])toArray(vec);
    
            // display result
    
            for (int i = 0; i < wrappedvec.length; i++) {
                System.out.println(wrappedvec[i]);
            }
        }
    }

The method "toArray" takes a single Object argument (arrays can be assigned to Object references). If the argument is null, does not represent an array, or represents an array of other than primitive type, the method simply returns the argument. java.lang.Class facilities are used to determine whether the argument is an array, and to obtain the array's underlying component type.

Once these checks are made, reflection facilities from the class java.lang.reflect.Array are used to obtain the length of the primitive array, and then obtain individual elements of the array. Each element obtained by Array.get is returned in a wrapper such as Integer or Double.

A final example builds on the previous one, and shows how you can use collection features with arrays. This assumes you already have an Object array. Here's the program:

    import java.util.Arrays;
    import java.util.List;

    public class ArrayDemo4 {
        public static void main(String args[]) {
            Object vec[] = {new Integer(37), new Integer(47)};
            List lst = Arrays.asList(vec);
            lst.set(1, new Integer(57));
            for (int i = 0; i < vec.length; i++) {
                System.out.println(vec[i]);
            }
        }
    }

In this program, vec is an Object array containing Integer(37) and Integer(47) objects. Then Arrays.asList is called. It returns a collection (of interface type List), with the array as the backing storage for the collection. In other words, a collection like ArrayList has within it some type of storage to store collection elements. In this example, the storage that is used is the passed-in array argument to Arrays.asList. This means that changes made by collection methods (such as set), are reflected in the underlying array.

Changing the element at index 1 in the collection causes the underlying array to change, and the output of running the program is:

    37
    57

So if you have an Object array, you can use collection features with it; the array itself serves as the underlying storage.

It's also possible to take a collection and convert it to an Object array, by saying:

    Object vec[] = lst.toArray();

For further information, see sections 11.2.9 Arrays, 16.6 List, and 16.9 The Arrays Utility Class in "The Java Programming Language Third Edition" by Arnold, Gosling, and Holmes
(http://java.sun.com/docs/books/javaprog/thirdedition/)


JAVA I/O REDIRECTION

If you've worked with UNIX or Windows shells (command processors) very much, you've probably used I/O redirection operators such as this:

    $ command >outfile

This usage says to run a command, and direct its standard output (such as that written by System.out.println) to the specified file instead of to the console or screen.

This feature is quite useful. However it's possible to achieve the same objective inside of a Java application, without relying on a shell. Typically, if you're using a programming style that relies on standard input and output (as the UNIX shell and utility programs do), you don't need or want to redirect I/O from inside a program. But sometimes there are exceptions to this rule. Let's look at a couple of examples.

The first example redirects standard output to a file:

    import java.io.*;
    
    public class RedirectDemo1 {
        public static void main(String args[]) throws IOException {
    
            // set up a PrintStream pointing at a file
    
            FileOutputStream fos =
                new FileOutputStream("out.txt");
            BufferedOutputStream bos =
                new BufferedOutputStream(fos, 1024);
            PrintStream ps =
                new PrintStream(bos, false);
    
            // redirect System.out to it
    
            System.setOut(ps);
    
            // do some output
    
            System.out.println("This is a test\u4321");
            int n = 37;
            System.out.println("The square of " + n +
                " is " + (n * n));
    
            ps.close();
        }
    }

Standard output (System.out) is a reference to an object of type PrintStream. To redirect output, an object of this type is created, representing a file or other output stream (such as a network connection). Then System.setOut is called to change the System.out reference.

As a point of interest, the JDK 1.3 implementation of java.lang.System has internal code of this form:

    FileOutputStream fdOut =
        new FileOutputStream(FileDescriptor.out);
    setOut0(new PrintStream(
        new BufferedOutputStream(fdOut, 128), true));

This code is used to initialize System.out. So, by default, System.out is a PrintStream. The PrintStream represents a special FileOutputStream file created from FileDescriptor.out, with a 128-byte buffer, and with autoflush set to true. Autoflush means that output is flushed on new line characters and when byte vectors are written. When output is redirected as in the example above, System.out is changed to reference the new PrintStream object that was created and passed to System.setOut.

There are a couple of issues related to the RedirectDemo1 program. One is that PrintStream converts characters to bytes using the platform's default character encoding. This is usually a good thing. For example, if you have a short Java program with the following line in it:

    System.out.println("this is a test");

and you run the program by saying (in the United States English locale):

    $ java prog >outfile

the program will direct ASCII characters to "outfile". This is probably what you want, given the prevalence of 7-bit ASCII text files.

But there's a problem in that the Unicode character '\u4321' in the demo program has turned into a single '?' byte. You can see the ? if you look at the out.txt file. In other words, the default encoding doesn't correctly handle one of the output characters.

Another issue is that I/O redirection can be generalized. You can, for example, send output to a string instead of to a file. Here's an example that covers both of these issues:

    import java.io.*;
    
    public class RedirectDemo2 {
        public static void main(String args[]) throws IOException {
    
            // set up a PrintWriter layered
            // on top of a StringWriter
    
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
    
            // write some output (to the StringWriter)
    
            pw.println("This is a test\u4321");
            int n = 37;
            pw.println("The square of " + n + " is " + (n * n));
    
            // get a string containing what was written
    
            String str = sw.toString();
    
            // display it
    
            System.out.print(str);
    
            // output the string to a file, using
            // the UTF-8 encoding
    
            FileOutputStream fos =
                new FileOutputStream("out.txt");
            OutputStreamWriter osw =
                new OutputStreamWriter(fos, "UTF-8");
            BufferedWriter bw =
                new BufferedWriter(osw);
            bw.write(str);
            bw.close();
    
            // read back the string and check
    
            FileInputStream fis =
                new FileInputStream("out.txt");
            InputStreamReader isr =
                new InputStreamReader(fis, "UTF-8");
            BufferedReader br =
                new BufferedReader(isr);
            String s1 = br.readLine();
            String s2 = br.readLine();
            br.close();
    
            String linesep = System.getProperty("line.separator");
            if (!str.equals(s1 + linesep + s2 + linesep))
                System.err.println("equals check failed");
        }
    }

The first part of the example sets up a PrintWriter object layered on top of a StringWriter. PrintWriter is similar to PrintStream, but operates on characters instead of byte streams. StringWriter is used to accumulate characters into a dynamic internal buffer for later retrieval into a String or StringBuffer.

After the output is written to the StringWriter, the accumulated string is retrieved. The string is then written to a file using OutputStreamWriter and the UTF-8 encoding. This encoding is supported in all Java implementations. It encodes Java characters in the range '\u0001' through '\u007f' as one byte; other characters are encoded as two or three bytes.

Finally, the string is read back from the file, again using the UTF-8 encoding. The string is then compared to the original string. The original string had two line separators in it, and so it is read back as two strings. The line separators are added to build up one string for comparison.

Note that you can also redirect input from a file or string, using classes such as StringReader.

Further reading: sections 9.7.1 Character Encoding, 15.4.7 String Character Streams, 15.4.8 Print Streams, and 18.1.1 Standard I/O Streams in "The Java Programming Language Third Edition" by Arnold, Gosling, and Holmes
(http://java.sun.com/docs/books/javaprog/thirdedition/)

— Note —

The names on the JDCSM mailing list are used for internal Sun MicrosystemsTM purposes only. To remove your name from the list, see Subscribe/Unsubscribe below.

— Feedback —

Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster

— Subscribe/Unsubscribe —

The JDC Tech Tips are sent to you because you elected to subscribe. To unsubscribe from this and any other JDC Email, select "Subscribe to free JDC newsletters" on the JDC front page. This displays the Subscriptions page, where you can change the current selections.

You need to be a JDC member to subscribe to the Tech Tips. To become a JDC member, go to:

http://java.sun.com/jdc/

To subscribe to the Tech Tips and other JDC Email, select "Subscribe to free JDC newsletters" on the JDC front page.

— Archives —

You'll find the JDC Tech Tips archives at:

http://developer.java.sun.com/developer/TechTips/index.html

— Copyright —

Copyright 2000 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This Document is protected by copyright. For more information, see:

http://developer.j ava.sun.com/developer/copyright.html


Print Button
[ This page was updated: 21-Sep-2000 ]
Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary | Feedback | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-2000 Sun Microsystems, Inc.
All Rights Reserved. Terms of Use. Privacy Policy.