Friday, February 10, 2023

Quiz yourself: Handling side effects in Java

Oracle Java, Oracle Java Tutorial and Materials, Oracle Java Prep, Oracle Java Preparation, Oracle Java Certification, Oracle Java Learning, Oracle Java Guides

This question exemplifies a style that’s popular with test creators. It’s less popular with candidates.


Imagine that your colleague is prototyping new business logic that must work in a multithreaded application and has created the following class:

class MyRunnable implements Runnable {
    public void run() {
        synchronized (MyRunnable.class) {
            System.out.print("hello ");
            System.out.print("bye ");
        }
    }
}

To test the class, your colleague wrote the following method and then invoked the method, passing a Stream object containing two MyRunnable instances:

public static void testMyRunnable(Stream<Runnable> s) {
    s.map(
        i -> {
            new Thread(new MyRunnable()).start();
            return i;
        }
    ).count();
}

A. The output will be exactly hello bye hello bye.
B. The output will always start with hello followed by either hello or bye.
C. No output will be produced.
D. None of the above.

Which statement is correct? Choose one.


Answer. This question exemplifies a style that’s popular with test creators, but perhaps it’s less popular with candidates. The setup makes the question appear to be on one topic, when in fact it’s really about something else. In this case, the question probably appears to be about threading and mutual exclusion using synchronization. It’s really about the Stream API.

Oracle Java, Oracle Java Tutorial and Materials, Oracle Java Prep, Oracle Java Preparation, Oracle Java Certification, Oracle Java Learning, Oracle Java Guides
Look at the method and its invocation. The test method receives a Stream as an argument, calls a map() operation on that stream, and then executes the count() terminal operation on the resulting stream. You know from the question that the Stream argument has two items in it, so the count() method must return 2.

Here is the detail that matters most: If the Stream object is one for which the size is known without having to draw elements to exhaustion, the count() method might actually return that size without ever processing the body of the stream. Indeed, the documentation for the count() method states the following:

An implementation may choose to not execute the stream pipeline (either sequentially or in parallel) if it is capable of computing the count directly from the stream source. In such cases no source elements will be traversed and no intermediate operations will be evaluated. Behavioral parameters with side-effects, which are strongly discouraged except for harmless cases such as debugging, may be affected.

In other words, if the argument stream has a known size, there will be no output at all. If, however, the argument stream has a size that is not known until it runs, some output will be produced.

The side effects of printing “hello ” and “bye ” are therefore not impossible but are also not guaranteed. Options A, B, and C are therefore incorrect, and option D must be the correct answer.

To dig deeper, let’s investigate this idea of a stream having a known or unknown element count. The following streams have exactly two elements:

List.of(1, 3).stream()
Stream.of(1, 3)

However, because some of the elements might be removed, the following stream has an element count that must be determined dynamically:

List.of(1, 3).stream.filter(x -> 3 * Math.random())

Given that this kind of side effect can be ignored—the documentation calls it elided—how should you write code intended to be used in the map method and related methods? The guidance is that the operations passed as arguments to the methods of a stream should generally be pure functions. A key (but not the only) feature of a pure function in programming (as distinct from mathematical theory) is that it does not have observable side effects. (Printing a message is typically considered to be a visible side effect, though logging messages might not be considered visible. It’s complicated and what’s visible depends a bit on perspective.)

On this topic, the documentation has more to offer.

The eliding of side-effects may also be surprising. With the exception of terminal operations forEach and forEachOrdered, side-effects of behavioral parameters may not always be executed when the stream implementation can optimize away the execution of behavioral parameters without affecting the result of the computation.

As mentioned earlier, this question looks as if it’s about synchronization. So, in the interest of completeness, consider how this aspect will behave if the map method’s argument is invoked with each element of the stream.

The body of the run() method is synchronized on the java.lang.Class object that describes MyRunnable in the running VM (that is, MyRunnable.class). This is, in effect, a static element and, therefore, no matter how many instances of this particular MyRunnable class might exist, only one thread can be in the process of executing the sequence of print statements. That tells you that if any thread manages to print “hello ” it must continue to print “bye ” before any other thread can print anything. This would mean that, if the stream actually processed its elements through the map operation, the output would be as shown in option A.

Conclusion. The correct answer is option D.

Source: oracle.com

Monday, February 6, 2023

Curly Braces #8: REST peacefully with GraphQL and Java

GraphQL can be a very efficient way of transferring data via API calls.


I’ve been RESTing happily since the early 2000s after Roy Fielding’s doctoral dissertation, “Architectural styles and the design of network-based software architectures,” caused many in the software world to move to representational state transfer (REST) to solve their API needs.

Oracle Java, Java Exam, Java Tutorial and Materials, Java certification, Java Prep, Java Preparation, Java Guides, Java Graph

Prior to that, I was building web-enabled software services, called service-oriented architecture (SOA) or web services. REST helped to formalize API definitions, but SOA and web services were essentially equivalent to traditional approaches in two key ways: The API developer predetermines both the endpoints and the data returned for each API.

Over the past few years, many have come to consider REST the de facto standard for API usage, even for noninternet applications. It’s easy to embed a web server to serve up a REST API, and there are plenty of frameworks available to enable it. Additionally, REST APIs are language- and platform-neutral, and those APIs are often used as a facade to enable legacy applications in a modern web or mobile application architecture. In this article, I’ll talk about both REST and another architecture, GraphQL.

REST has drawbacks


Although REST solves many API-related problems, it’s not perfect. The architecture’s deficiencies include the following.

Overfetching. REST APIs are defined to return data as a predefined structure, usually in XML or JSON. If a caller wants only some fields of data returned, too bad: They get all the data anyway. This doesn’t seem like a big deal, but this inefficiency adds up when an API returns multiple records.

Underfetching. You may need to make multiple REST calls to aggregate all the data you need for one user or back-end operation. The associated round trips are inefficient and can lead to multiple database transactions.

Overfetching and underfetching. Ironically, underfetching often leads to overfetching, because one or more of the REST calls required to satisfy a single user operation likely contain data that’s not needed or that’s duplicated..

Implicit intent. REST is built upon HTTP, and it leverages GET and PUT/POST calls to indicate read or write operations. With REST, it’s frowned upon to name API calls explicitly, for example, GetUser or CreateUser. Instead, you are encouraged to name the API and the user, and then rely on the HTTP operation that’s used to imply the intent. For example, an HTTP GET is equivalent to GetUser, PUT is equivalent to either UpdateUser or CreateUser, a POST is usually equivalent to CreateUser but sometimes to UpdateUser, and DELETE is equivalent to DeleteUser. Because of this, the API’s intent can be hidden behind the communication protocol, so it isn’t always obvious. It’s also not a precise match; hence, the confusion between POST, PUT, and PATCH.

Lack of agility. Each REST API call exists and returns the prescribed data only because its creator decided it should. Even if the API is well designed, it’s unlikely to serve every client’s needs precisely, and changing needs will render it less of a fit over time. Additionally, once APIs are used, it’s difficult or impossible to change them without impacting external applications. Building dependencies between applications is less than agile.

Introducing GraphQL


In 2012, developers at Facebook developed an improvement on REST, which was then released as an open source data query language called GraphQL.

GraphQL is similar to REST except that it’s data oriented: The caller precisely defines the data to be returned, and the server complies by returning that data and nothing else. For instance, if a user wants to know the balance for a bank account, the front-end code will make a call to a GraphQL web interface using a JSON-like request such as the one shown in Listing 1. (The ssn field is for a nine-digit identifier issued by the US government called a Social Security Number.)

Listing 1. A sample GraphQL query

{
    account {
        id(id: "987654321")
        name
        type
        customer {
            firstName
            lastName
            ssn
        }
        availableBalance
        totalBalance
    }
}

The server will fulfill the query with a JSON-compliant response, as shown in Listing 2.

Listing 2. A sample GraphQL query response

{
  "data": {
    "account": {
      "id": "987654321",
      "name": "Personal Checking",
      "type": "Basic Checking",
      "customer": {
        "firstName": "Eric",
        "lastName": "Bruno",
        "ssn": "123-45-6789"
      },
      "availableBalance": "1234.56",
      "totalBalance": "1234.56"
    }
  }
}

In this case, the identification of the bank account is provided as an input key for lookup. So far, this is a straightforward query. However, consider that this single GraphQL call combines data from multiple resources: the user as well as basic account and balance information from the bank.

By contrast, common REST APIs often break this into multiple endpoints and calls: one for the balance of the given account number, another for account data, and yet another for user data. Additionally, there’s likely a lot more data about the account and the user than what was returned here.

Individual REST calls to get user and account information would likely have resulted in overfetching, which is inefficient and may even be a security risk in a financial application.

Looking inside GraphQL


Although GraphQL’s name contains the word graph, the architecture doesn’t supply true graph operations. However, GraphQL does provide a type system with introspection, a defined query language, and execution semantics with explicit indication of reads and writes. A single request, called a query, can return data for more than one resource, as shown in the previous example, by following references between them.

In other words, GraphQL queries allow you to express relationships in the call itself, dynamically, offering efficiency and flexibility.

Unlike REST APIs, which use endpoints to describe and group operations, GraphQL organizes them by schemas, data types, and associated fields. Types are used to constrain requests to only what is feasible, and they indicate how data is to be used. Using the query in Listing 1, related GraphQL types might look like Listing 3.

Listing 3. GraphQL types for the query in Listing 1

type Query {
    account: Account
}

type Account {
    id: Int
    name: String
    type: [
        "Basic Checking"
        "Advanced Checking"
        "Business Checking"
    ]
    owner: Customer
    availableBalance: Balance
    totalBalance: Balance
}

type Customer {
    firstName: String
    lastName: String
    ssn: String
    address: Address
    phone: Phone
    email: String
    active: Boolean
}

type Address {
    street: String
    city: String
    state: [
      "Alabama"
      "Alaska"
      ...
    ]
    zip: String
}

type Phone {
    ...
}

type Balance {
    amount: Float
    asOf: Date
    ...
}

As shown in this example, the GraphQL type system is expressive and comprehensive.

GraphQL mutations


Notice that the GraphQL description for type in Listing 3 begins with the keyword Query. This indicates that this is a read schema. GraphQL provides the mutation schema to mark an API as writable. It’s a requirement that every GraphQL API have a query type, but a mutation type is optional, and it is similar to queries in that you specify nested fields and a return type. The following is an example of the mutation type definition:

mutation CreateAccount($account: Account,) {
    createAccount(account: $account) {
        id
        name
    }
}

The createAccount mutation creates a new account and returns the id and name of that account. The matching request, which is an input object type, would look like the following:

{
  "account": {
    "name": "Personal Checking",
    "type": "Basic Checking",
    "customer": {
    "firstName": "Eric",
    "lastName": "Bruno",
    "ssn": "...",
    "address": "...",
    "phone": "...",
    "email": "eric@ericbruno.com",
    "active": "true"

    }
  }
  :
}

The result would be the new account id and name, as shown below.

{
  "data": {
    "createAccount": {
      "id": "987654321",
      "name": "Personal Checking",
      "Customer:" {
        "ssn": "..."
      }
    }
  }
}

The mutation in this example can create a new customer along with the account or return an existing customer if the record is located with the ssn provided; GraphQL is flexible this way.

The GraphQL schema includes more advanced features, such as interfaces, lists, the ability to specify bounds on fields, enumerations, unions, inputs, operations, and more. There’s also a sophisticated validation schema based on the GraphQL type system.

Java and GraphQL


GraphQL includes open source helper code in many languages, including Java, to make it easy to create and consume GraphQL APIs.

On GitHub, you’ll find Java classes to help generate queries, define schemas, execute queries, and parse the results. Other GraphQL Java libraries are available and also integrate with other tools and server frameworks such as Spring.

Source: oracle.com

Sunday, January 22, 2023

Oracle 1Z0-811 Certification: The Way to Get Powerful Achievement

1z0-811 dumps, 1z0-811, 1z0-811 study guide pdf, 1z0-811 practice test, java foundations 1z0-811 questions, java foundations 1z0-811 pdf, 1z0-811 exam questions, java foundations 1z0-811, java foundations 1z0-811 dumps, java foundations oracle

Qualifying for the Oracle Java Foundations (1Z0-811) exam leads the candidates to earn the Java Foundations Certified Junior Associate credentials. The process provides the candidate with the fundamentals of Java programming, enabling them to showcase their conceptual understanding and abilities.

This Oracle 1Z0-811 certification validates the candidate’s capabilities to a future employer, bestowing their potential to become an increasingly valuable asset to any organization as they progress into the OCA level during their early stage of employment and later to OCP.

The Java Foundations certification is also called the Oracle Certified Foundations Associate exam. Earning the associated certification means that you are competent with the fundamentals of Java programming, enabling you to demonstrate both conceptual knowledge and practical skills.

Some Tips for How to Get Oracle 1Z0-811 Certification?

The Oracle 1Z0-811 certification exam is one of the most well-known credentials that help professionals advance their careers. Acquiring this certificate can give your career a new perspective and direction, and it connects you with high-paying opportunities in a variety of sectors around the world.

1. Creating Your Study Plan

Consider two main factors when developing your study plan: Budget and Time.

How much you intend to invest in your Oracle 1Z0-811 exam preparation is essential to your overall study plan. Determining how much you are prepared to invest in the exam preparation will help you determine how much material you will tackle.

Setting a precise budget for resources, training, courses, simulators, etc., will help you create an accurate schedule of the material you are going through for your preparation. Start with setting up a specific budget, and then research the best resources you can rely on for studying.

2. Familiarize Yourself with the Evaluation Information

Before moving any further, it is wise to gather crucial information about the actual exam, figure out the eligibility criteria, and understand its format. This knowledge is required to create a proper study plan and test success strategy.

3. Join the Brigade

The 1Z0-811 certification aspirants have an added advantage over others regarding community support. Before you, more than 1,000,000 people have already earned this prestigious credential and are ready to offer a helping hand to those who have just embarked on or planning to commence the 1Z0-811 certification journey. In particular, the Oracle website and the LinkedIn group are two highly dependable places to be when you yearn for real-world, verified, and practical exam prep advice.

4. Add the Online 1Z0-811 Exam Simulators to Your Prep Strategy

There is an adage that says practice makes perfect. The Oracle 1Z0-811 exam is one of the most challenging exams in the world. As part of your preparation, you should practice doing several sample questions.

Taking practice exams is an integral part of preparing for any exam, and it is all the more crucial for the 1Z0-811 certification. So, online 1Z0-811 simulators will enable you to assess your preparation level while increasing your confidence and diligence to tackle exam pressure.

5. Flashcards for the Win

With this incredibly compact and cost-effective strategy, you are prepared and fired up to comprehend more complex ideas in an enjoyable manner that stimulates your mind. Making your own Oracle 1Z0-811 exam flashcards can also greatly assist when you are studying. However, electronic ones also function nicely if time is of the essence.

6. Community Learning

Discussion boards and study groups are excellent tools for improving retention. You can assist others, get your 1Z0-811 exam questions answered, and pick up time-saving tips and tricks by actively participating in them.

7. Give the 1Z0-811 Exam and Believe in Yourself

You will pass with flying colors as long as you are passionate about managing client expectations, developing a detailed project plan, defining the project's scope, and assigning team members to specific tasks.

At the core of this 1Z0-811 certification and the career path of passing an Oracle Java Foundations lies excellent communication skills, negotiating, conflict resolution, and promoting teamwork. Working on your interpersonal and presentation skills is a great way to stand apart in your exam and get noticed.

Final Say

No certification journey is easy, and the strict and tedious structure of the 1Z0-811 certification test makes it a tough nut to crack. However, as mentioned earlier, the right attitude carved after referring to the pointers can crash it. So, follow the above tips and win over all difficulties.

Best of luck!

Friday, January 20, 2023

Minborg’s Java Pot

Did you know you can allocate memory segments that are larger than the physical size of your machine’s RAM and indeed larger than the size of your entire file system? Read this article and learn how to make use of mapped memory segments that may or may not be “sparse” and how to allocate 64 terabytes of sparse data on a laptop.

Mapped Memory


Mapped memory is virtual memory that has been assigned a one-to-one mapping to a portion of a file. The term “file” is quite broad here and may be represented by a regular file, a device, shared memory or any other thing that the operating system may refer to via a file descriptor.

Accessing files via mapped memory is often much faster than accessing a file via the standard file operations like read and write. Because mapped memory is operated on directly, some interesting solutions can also be constructed via atomic memory operations such as compare-and-set operations, allowing very efficient inter-thread and inter-process communication channels. 

Because not all parts of the mapped virtual memory must reside in real memory at the same time, a mapped memory segment might be much larger than the physical RAM in the machine it is running in. If a portion of the mapped memory is not available when accessed, the operating system will temporarily suspend the current thread and load the missing page after which operation may resume again.

Other advantages of mapped files are; they can be shared across processes running different JVMs and, the files remain persistent and can be inspected using any file tool like hexdump.

Setting up a Mapped Memory Segment


The new Foreign Function and Memory feature that previews for the second time in Java 20 allows large memory segments to be mapped to a file. Here is how you can create a memory segment of size 4 GiB backed by a file.

Set<OpenOption> opts = Set.of(CREATE, READ, WRITE);
try (FileChannel fc = FileChannel.open(Path.of("myFile"), opts);
     Arena arena = Arena.openConfined()) {
 
    MemorySegment mapped = 
 
            fc.map(READ_WRITE, 0, 1L << 32, arena.scope());
    use(mapped);
} // Resources allocated by "mapped" is released here via TwR

Sparse Files


A sparse file is a file where information can be stored in an efficient way if not all portions of the file are actually used. A file with large unused “holes” is an example of such a file whereby only the used sections are actually stored in the underlying physical file. In reality, however, the unused holes also consume some resources albeit much less than their used counterparts.

Java Pot, Oracle Java, Oracle Java Exam, Oracle Java Exam Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Career, Oracle Java Skills, Oracle Java Jobs
Figure 1, Illustrates a logical sparse file where only actual data elements are stored in the physical file.

As long as the sparse file is not filled with too much data, it is possible to allocate a sparse file that is much larger than the available physical disk space. For example, it is possible to allocate an empty 10 TB memory segment backed by a sparse file on a filesystem with very little available capacity. 

It should be noted that not all platforms support sparse files.

Setting up a Sparsely Mapped Memory Segment 


Here is an example of how to create and access the Contents of a file via a memory-mapped MemorySegment whereby the Contents is sparse. For example, expanding the real underlying data in the file as needed automatically:

Set<OpenOption> sparse = Set.of(CREATE_NEW, SPARSE, READ, WRITE);
 
try (var fc = FileChannel.open(Path.of("sparse"), sparse);
 
     var arena = Arena.openConfined()) {
 
     memorySegment mapped = 
 
             fc.map(READ_WRITE, 0, 1L << 32, arena.scope());
 
    use(mapped);
 
} // Resources allocated by "mapped" is released here via TwR

Note: The file will appear to consist of 4 GiB of data but in reality the file does not use any (apparent) file-system space at all:

pminborg@pminborg-mac ntive % ll sparse 

-rw-r–r–  1 pminborg  staff  4294967296 Nov 14 16:12 sparse

pminborg@pminborg-mac ntive % du -h sparse 

  0B sparse

Going Colossal


Java Pot, Oracle Java, Oracle Java Exam, Oracle Java Exam Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Career, Oracle Java Skills, Oracle Java Jobs
The implementation of sparse files varies across the many platforms that are supported by Java and consequently, various sparse-file properties will vary depending on where an application is deployed.

I am using a Mac M1 under macOS Monteray (12.6.1) with 32 GiB RAM and 1 TiB storage (of which 900 GiB are available). 

I was able to map a single sparse file of up to 64 TiB using a single mapped memory segment on my machine (using its standard settings):

  4 GiB -> ok as demonstrated above

  1 TiB -> ok

 32 TiB -> ok

 64 TiB -> ok

128 TiB -> failed with OutOfMemoryError

It is possible to increase the amount of mappable memory but this is out of the scope for this article. In real applications, it is better to have smaller portions of a sparse file mapped into memory rather than mapping the entire sparse file in one chunk. These smaller mappings will then act as “windows” into the larger underlying file.

 Anyhow, this looks pretty colossal:

-rw-r–r–   1 pminborg  staff  70368744177664 Nov 22 13:34 sparse

Creating the empty 64 TiB sparse file took about 200 ms on my machine.

Unrelated Observations on Thread Confinement


As can be seen above, it is possible to access the same underlying physical memory from different threads (and indeed even different processes) with file mapping despite being viewed through several distinct thread-confined MemorySegment instances.

Source: javacodegeeks.com

Wednesday, January 18, 2023

Quiz yourself: Multithreading and the Java keyword synchronized

The goal is to obtain consistent results and avoid unwanted effects.


Imagine that you are working with multiple instances of the following SyncMe class, and the instances are used by multiple Java threads:

Quiz Yourself, Multithreading, Java Keyword Synchronized, Oralce Java Certification, Java Prep, Java Preparation, Java Tutorial and Materials

public class SyncMe {
    protected static synchronized void hi() {
        System.out.print("hi ");
        System.out.print("there! ");
    }
    public synchronized void bye() {
        System.out.print("bye ");
        System.out.print("there! ");
    }
    public synchronized void meet() {
        hi();
        bye();
    }
}

What statements are true about the class? Choose two.

A. Concurrent calls to the hi() methods can sometimes print hi hi.

B. Concurrent calls to the bye() methods can sometimes print bye bye.

C. Concurrent calls to the meet() method always print hi there! bye there!.

D. Concurrent calls to the meet() method can print bye bye.

E. Concurrent calls to the meet() method can print hi hi.

Answer. This question investigates the meaning and effect of the keyword synchronized and the possible behavior of code that uses it in a multithreaded environment.

One fundamental aspect of the keyword synchronized is that it behaves rather like a door.

◉ When a thread encounters such a door, it cannot execute past that point unless that thread carries, or can obtain, the right key to open the door.

◉ When the thread enters the region behind the door (the synchronized block), it keeps the key until it exits that region.

◉ When the thread exits the synchronized block, the thread is supposed to put the key back on the hook, meaning that another thread could potentially take the key and pass through the door.

Upon simple analysis, this behavior prevents any other thread from passing through that door into the synchronized block while the first thread is executing behind the door.

(This discussion won’t go into what happens if the key were already held by the thread at the point when it reached the door. Although that’s important to understand in the big scheme, it’s not necessary for this question because it does not happen in this example. Frankly, we’re also ignoring quite a bit of additional complexity that can arise in situations more complex than this question presents.)

In the real world, of course, it’s possible that several doors might require the same key or they might require different keys. The same is true in Java code, and for this question you must understand the different keys and the doors those keys open. Then you must think through how the code might behave when it’s run in a multithreaded environment.

The general form of the keyword synchronized is that it takes an object as a parameter, such as the following:

void doSyncStuff() {
  synchronized(this.rv) {
    // inside
  }
}

In this situation, the key required to open the door and enter the synchronized block is associated with the object referred to by the this.rv field. When a thread reaches the door, and assuming it doesn’t already have the key, it tries to take that key from the hook, which is that object. If the key is not on that hook, the thread waits until after the key is returned to that hook.

In the code for this question, it is crucial to realize that if there are two instances of the enclosing object and a different thread is executing on each of those instances, it’s likely there are two different keys: one for the door that’s encountered by one thread and another for the door encountered by the other thread. This is potentially confusing since it’s the same line of code, but the key required to open the door depends on the object referred to by this.rv.

Of course, the code for this question does not have a parameter after the keyword synchronized. Instead, synchronized is used as a modifier on the method. This is effectively a shortcut.

To explain, if the method is a static method, such as this

synchronized static void dSS() {
  // method body
}

and the enclosing class is MySyncClass, then the code is equivalent to this

static void dSS() {
  synchronized (MySyncClass.class) {
    // method body
  }
}

Notice that in this case, all the static synchronized methods in a single class will use the same key.

However, if the method is a synchronized instance method, like this

synchronized void dIS() {
  // method body
}

then it is equivalent to this

void dIS() {
  synchronized(this) {
    // method body
  }
}

It’s critical to notice that if you have two threads executing this same method on different object instances, different keys are needed to open the doors.

Given this discussion and noting that the hi() method is static but the other two are instance methods, and also that the question states that multiple objects exist, recognize that only one thread at a time can be executing the hi() method, but more than one thread might be executing the other two methods.

That tells you that whenever hi has been printed, another hi cannot be printed until after the printing of there!. You might see any of the output from invocations of the bye() method between hi and there!, but you’ll never see hi hi printed. From that you know that option A is incorrect.

Using the same logic as above, concurrent calls to meet() cannot result in hi hi being printed either, since that output is impossible no matter how the hi() method is invoked. That means that option E must also be incorrect.

By contrast, concurrent calls to the bye() method can execute concurrently if they are invoked on different instances of the class. In such a situation the output of the two invocations can become interleaved, and you might in fact see bye bye printed. That makes option B correct, and at the same time and for the same reason, it makes D correct, because concurrent calls to meet() can result in concurrent calls to the bye() method.

Option C must be incorrect, because it contradicts the notion that you can ever see bye bye printed.

Conclusion. The correct answers are options B and D.

Source: oracle.com

Friday, January 13, 2023

Quiz yourself: Understanding the syntax of Java’s increment and decrement operators

Java’s increment and decrement operators, Oracle Java Career, Java Skills, Java Jobs, Java Tutorial and Materials, Oracle Java Preparation, Oracle Java Guides

Do Java expressions such as ii[++i] = 0, ii[i++]++, or i = +(i--) give you a headache?


Given the following Calc class

class Calc {
    Integer i;
    final int[] ii = {0};
    {
        ii[++i] = 0; // line 1
        i--;         // line 2
        ii[i++]++;   // line 3
        (i--)--;     // line 4
        i = +(i--);  // line 5
    }
}

Which statement is correct? Choose one.

A. Compilation fails at line 1 only.

B. Compilation fails at line 2 only.

C. Compilation fails at line 3 only.

D. Compilation fails at line 4 only.

E. Compilation fails at line 5 only.

F. Compilation fails at more than one line.

G. All lines compile successfully.

Answer. This question tests your knowledge of syntax for a variety of expressions.

First, notice there are two object field variables.

◉ The first is an uninitialized field of Integer type, called i. Because this is an uninitialized object field, it would have a null value when it is initialized. However, the compiler does not care about this; from that perspective, it’s simply a variable and the lack of explicit initialization is irrelevant.

◉ The second field is an int array called ii. This field is marked final and is initialized with an array literal containing a single element having a value of zero. It’s important to remember that marking the field as final merely means the field can never be modified to refer to any other array. It does not prevent the elements of the array from being changed (though it’s also true that you can never grow or shrink an array in Java).

Java’s increment and decrement operators, Oracle Java Career, Java Skills, Java Jobs, Java Tutorial and Materials, Oracle Java Preparation, Oracle Java Guides
Next, consider the increment and decrement operator usage. It might cause some concern that these are applied to the variable i, which is of Integer type. After all, Integer objects are immutable. However, this is not a problem. All that happens is that the contents of the variable are unboxed, the resulting int is incremented or decremented, and then a new Integer is created with that new value. Finally, the reference to the new Integer is assigned to the variable.

Look at line 1 considering the information above. The effect would be to assign zero to an element of the array at a subscript one greater than the int value of i before executing the line. A second effect would be that the int value in the object referred to by i would now be one greater than before. This is all valid syntax, even though the code couldn’t run correctly, because it would fail with a NullPointerException. However, the question doesn’t ask about running the code, only about compiling it. Also note that under some conditions, code of this kind could fail at runtime with an ArrayIndexOutOfBoundsException. Again, this is not relevant in this question. From this, you can see that line 1 compiles correctly.

A similar analysis of line 2 reveals that if i were not null, this line would reassign the variable i to refer to a new Integer object with an int value one less than the Integer to which it previously referred. Even though the code of line 2 would not execute, it would compile correctly.

In line 3, the code would increment the array element at the index value indicated by the current value of i, and then reassign i to have an int value one greater than before. This would fail with a NullPointerException, and if that were rectified, the code might still fail if the subscript indicated by i were invalid. However, the code of line 3 is syntactically valid and would compile without error.

Line 4 would fail to compile. One of the requirements for using the increment and decrement operators is that the target of such an operator must be in storage that can be updated. Such a value is sometimes referred to as an l-value, meaning an expression that can be on the left side of an assignment. In line 4, the expression is (i--)-- and the problem is that while i-- is valid in itself, the resulting expression is simply the numeric value that is contained in the object to which i now refers. And, in the same way that you cannot write 3++ (where would you store the result?), you cannot increment or decrement such a simple expression. The parenthetical (i--) cannot store the result. Consequently line 4 is syntactically invalid and would not compile.

Line 5 is syntactically valid. If i were not null, that line would reassign i to an Integer representing one less than the previous object it referred to, then apply the (no-effect) unary plus operator, and then assign that same result to the value i. The latter two operations have no meaningful effect, but they are syntactically valid, and line 5 would compile without problems.

In light of the foregoing discussions, you can see that option D is correct, and options A, B, C, E, F, and G are all incorrect.

Conclusion. The correct answer is option D.

Source: oracle.com