[go: up one dir, main page]

0% found this document useful (0 votes)
291 views32 pages

Exception Handling 1

Exception Handling is used to prevent programs from abruptly terminating due to runtime errors. Exceptions represent problems that occur during program execution. There are different types of exceptions including exceptions thrown by the Java Virtual Machine (JVM) like NullPointerException and exceptions thrown by application code like IllegalArgumentException. Exception handling uses keywords like try, catch, finally, throw and throws to control program flow when exceptions occur. The try block contains code that might throw exceptions, and catch blocks handle specific exceptions, while finally blocks contain cleanup code.

Uploaded by

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

Exception Handling 1

Exception Handling is used to prevent programs from abruptly terminating due to runtime errors. Exceptions represent problems that occur during program execution. There are different types of exceptions including exceptions thrown by the Java Virtual Machine (JVM) like NullPointerException and exceptions thrown by application code like IllegalArgumentException. Exception handling uses keywords like try, catch, finally, throw and throws to control program flow when exceptions occur. The try block contains code that might throw exceptions, and catch blocks handle specific exceptions, while finally blocks contain cleanup code.

Uploaded by

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

Exception Handling

Exception Handling is the mechanism to handle runtime malfunctions. We need to handle such
exceptions to prevent abrupt termination of program. The term exception means exceptional
condition, it is a problem that may arise during the execution of program. A bunch of things can
lead to exceptions, including programmer error, hardware failures, files that need to be opened
cannot be found, resource exhaustion etc.
Exception
A Java Exception is an object that describes the exception that occurs in a program. When an
exceptional events occurs in java, an exception is said to be thrown. The code that's responsible for
doing something about the exception is called an exception handler.
When an exception can occur?
Exception can occur at runtime (known as runtime exceptions) as well as at compile-time (known
Compile-time exceptions).
Reasons for Exceptions
There can be several reasons for an exception. For example, following situations can cause an
exception Opening a non-existing file, Network connection problem, Operands being
manipulated are out of prescribed ranges, class file missing which was supposed to be loaded and
so on.
Difference between error and exception
Errors indicate serious problems and abnormal conditions that most applications should not try to
handle. Error defines problems that are not expected to be caught under normal circumstances by
our program. For example memory error, hardware error, JVM error etc
Exceptions are conditions within the code. A developer can handle such conditions and take
necessary corrective actions. Few examples

DivideByZero exception
NullPointerException
ArithmeticException
ArrayIndexOutOfBoundsException

Advantages of Exception Handling


Exception handling allows us to control the normal flow of the program by using exception
handling in program.

It throws an exception whenever a calling method encounters an error providing that the
calling method takes care of that error.

It also gives us the scope of organizing and differentiating between different error types
using a separate block of codes. This is done with the help of try-catch blocks.

Why to handle exception?


If an exception is raised, which has not been handled by programmer then program execution can
get terminated and system prints a non user friendly error message.
Example:-Take a look at the below system generated exception
An exception generated by the system is given below
Exception in thread "main" java.lang.ArithmeticException: / by zero at
ExceptionDemo.main(ExceptionDemo.java:5)
ExceptionDemo : The class name
main : The method name
ExceptionDemo.java : The filename
java:5 : Line number
For a novice user the above message wont be easy to understand. In order to let them know that
what went wrong we use exception handling in java program. We handle such conditions and
then prints a user friendly warning message to user, which lets them correct the error as most of
the time exception occurs due to bad data provided by user.

Exception class Hierarchy


All exception types are subclasses of class Throwable, which is at the top of exception class
hierarchy.

E
x
c
e
p
t
i
o
n
c
l
a
s
s

is for exceptional conditions that program should catch. This class is extended to create user
specific exception classes.
RuntimeException is a subclass of Exception. Exceptions under this class are automatically
defined for programs.

Exception are categorized into 3 category.

Checked Exception
The exception that can be predicted by the programmer.Example : File that need to be opened is
not found. These type of exceptions must be checked at compile time.

Unchecked Exception
Unchecked exceptions are the class that extends RuntimeException. Unchecked exception are
ignored at compile time. Example : ArithmeticException, NullPointerException, Array Index out
of Bound exception. Unchecked exceptions are checked at runtime.

Error
Errors are typically ignored in code because you can rarely do anything about an
error. Example : if stack overflow occurs, an error will arise. This type of error is not possible
handle in code.

Uncaught Exceptions
When we don't handle the exceptions, they lead to unexpected program termination. Lets take an
example for better understanding.
class UncaughtException
{
public static void main(String args[])
{
int a = 0;
int b = 7/a; // Divide by zero, will lead to exception
}
}
This will lead to an exception at runtime, hence the Java run-time system will construct an
exception and then throw it. As we don't have any mechanism for handling exception in the above
program, hence the default handler will handle the exception and will print the details of the
exception on the terminal.

Common scenarios where exceptions may occur


There are given some scenarios where unchecked exceptions can occur. They are as follows:
1) Scenario where ArithmeticException occurs
If we divide any number by zero, there occurs an ArithmeticException.
1. int a=50/0;

//ArithmeticException

2) Scenario where NullPointerException occurs


If we have null value in any variable, performing any operation by the variable occurs an
NullPointerException.

1. String s=null;
2. System.out.println(s.length());

//NullPointerException

3) Scenario where NumberFormatException occurs


The wrong formatting of any value, may occur NumberFormatException. Suppose I have a string
variable that have characters, converting this variable into digit will occur
NumberFormatException.

1. String s="abc";
2. int i=Integer.parseInt(s);

//NumberFormatException

4) Scenario where ArrayIndexOutOfBoundsException occurs


If you are inserting any value in the wrong index, it would result
ArrayIndexOutOfBoundsException as shown below:

1. int a[]=new int[5];


2. a[10]=50;
//ArrayIndexOutOfBoundsException

Common Exceptions:
In Java, it is possible to define two catergories of Exceptions and Errors.

JVM Exceptions: - These are exceptions/errors that are exclusively or logically thrown by
the JVM. Examples : NullPointerException, ArrayIndexOutOfBoundsException,
ClassCastException,

Programmatic exceptions: - These exceptions are thrown explicitly by the application or


the API programmers Examples: IllegalArgumentException, IllegalStateException.

Java Exception Handling Keywords


There are 5 keywords used in java exception handling.
1.
2.
3.
4.
5.

try
catch
finally
throw
throws

Java try-catch

Java try block


Java try block is used to enclose the code that might throw an exception. It must be used within
the method.
Java try block must be followed by either catch or finally block.
Syntax of java try-catch

Try
{
//code that may throw exception
}catch(Exception_class_Name ref)
{}
Syntax of try-finally block

Try
{
//code that may throw exception
}
finally{}
Java catch block
Java catch block is used to handle the Exception. It must be used after the try block only.
You can use multiple catch block with a single try.

Problem without exception handling


Let's try to understand the problem if we don't use try-catch block.

public class Testtrycatch1


{
public static void main(String args[])
{
int data=50/0;//may throw exception
System.out.println("rest of the code...");
}
}
Output:
Exception in thread main java.lang.ArithmeticException:/ by zero
As displayed in the above example, rest of the code is not executed (in such case, rest of the code...
statement is not printed).
There can be 100 lines of code after exception. So all the code after exception will not be executed.

Solution by exception handling


Let's see the solution of above problem by java try-catch block.

public class Testtrycatch2


{

public static void main(String args[])


{
try
{
int data=50/0;
}
catch(ArithmeticException e)
{
System.out.println(e);
}
System.out.println("rest of the code...");
}
}
Output:
Exception in thread main java.lang.ArithmeticException:/ by zero
rest of the code...
Now, as displayed in the above example, rest of the code is executed i.e. rest of the code...
statement is printed.

Internal working of java try-catch block

The JVM firstly checks whether the exception is handled or not. If exception is not handled, JVM provides a
default exception handler that performs the following tasks:
o Prints out exception description.
o Prints the stack trace (Hierarchy of methods where the exception occurred).
o Causes the program to terminate.
But if exception is handled by the application programmer, normal flow of the application is
maintained i.e. rest of the code is executed.
Java catch multiple exceptions

Java Multi catch block


If you have to perform different tasks at the occurrence of different Exceptions, use java multi
catch block.
Simple example of java multi-catch block.

public class TestMultipleCatchBlock


{
public static void main(String args[])
{
try
{
int a[]=new int[5];
a[5]=30/0;
}
catch(ArithmeticException e){System.out.println("task1 is completed");
}
catch(ArrayIndexOutOfBoundsException e){System.out.println("task 2 completed"
);
}
catch(Exception e){System.out.println("common task completed");
}
System.out.println("rest of the code...");
}
}
Output: task1 completed
rest of the code...
Rule: At a time only one Exception is occured and at a time only one catch block is executed.
Rule: All catch blocks must be ordered from most specific to most general i.e. catch for
ArithmeticException must come before catch for Exception .

class TestMultipleCatchBlock1
{
public static void main(String args[]){
try
{
int a[]=new int[5];
a[5]=30/0;
}
catch(Exception e)
{
System.out.println("common task completed");
}
catch(ArithmeticException e)
{
System.out.println("task1 is completed");

}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.println("task 2 completed");
}
System.out.println("rest of the code...");
}
}
Output:
Compile-time error
Java Nested try block
The try block within a try block is known as nested try block in java.
Why use nested try block
Sometimes a situation may arise where a part of a block may cause one error and the entire block
itself may cause another error. In such cases, exception handlers have to be nested.
Syntax:
....
try
{
statement 1;
statement 2;
try
{
statement 1;
statement 2;
}
catch(Exception e)
{
}
}
catch(Exception e)
{
}
....

Java nested try example


Let's see a simple example of java nested try block.
class Excep6
{
public static void main(String args[]){
try
{
try
{
System.out.println("going to divide");
int b =39/0;
}
catch(ArithmeticException e)
{
System.out.println(e);
}
try
{
int a[]=new int[5];
a[5]=4;
}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.println(e);
}
System.out.println("other statement);
}
catch(Exception e)
{
System.out.println("handeled");
}
System.out.println("normal flow..");
}
}

Java finally block


Java finally block is a block that is used to execute important code such as closing connection, stream
etc.
Java finally block is always executed whether exception is handled or not.
Java finally block follows try or catch block.

Note: If you don't handle exception, before terminating the program, JVM executes finally
block(if any).

Why use java finally


o

Finally block in java can be used to put "cleanup" code such as closing a file, closing
connection etc.

Usage of Java finally


Let's see the different cases where java finally block can be used.
Case 1
Let's see the java finally example where exception doesn't occur.

class TestFinallyBlock{
public static void main(String args[]){
try{
int data=25/5;
System.out.println(data);
}
catch(NullPointerException e)
{
System.out.println(e);
}
finally
{
System.out.println("finally block is always executed");
}
System.out.println("rest of the code...");
}
}
Output:5
finally block is always executed
rest of the code...
Case 2
The java finally example where exception occurs and not handled.

class TestFinallyBlock1{
public static void main(String args[])
{
try
{
int data=25/0;
System.out.println(data);
}
catch(NullPointerException e)

{
System.out.println(e);
}
finally
{
System.out.println("finally block is always executed");
}
System.out.println("rest of the code...");
}
}
Output:
finally block is always executed
Exception in thread main java.lang.ArithmeticException:/ by zero
Case 3
The java finally example where exception occurs and handled.

public class TestFinallyBlock2{


public static void main(String args[])
{
try
{
int data=25/0;
System.out.println(data);
}
catch(ArithmeticException e)
{
System.out.println(e);
}
finally
{
System.out.println("finally block is always executed");
}
System.out.println("rest of the code...");
}
}
Output:
Exception in thread main java.lang.ArithmeticException:/ by zero
finally block is always executed
rest of the code...

Rule: For each try block there can be zero or more catch blocks, but only one finally block.
Note: The finally block will not be executed if program exits(either by calling System.exit() or
by causing a fatal error that causes the process to abort).

Java throw exception

Java throw keyword


The Java throw keyword is used to explicitly throw an exception.
We can throw either checked or uncheked exception in java by throw keyword. The throw
keyword is mainly used to throw custom exception. We will see custom exceptions later.
The syntax of java throw keyword is given below.

1.

throw exception;

The example of throw IOException.

2.

throw new IOException("sorry device error);

java throw keyword example


In this example, we have created the validate method that takes integer value as a parameter. If the
age is less than 18, we are throwing the ArithmeticException otherwise print a message welcome
to vote.

public class TestThrow1


{
static void validate(int age)
{
if(age<18)
throw new ArithmeticException("not valid");
else
System.out.println("welcome to vote");
}
public static void main(String args[])

{
validate(13);
System.out.println("rest of the code...");
}
}
Output:
Exception in thread main java.lang.ArithmeticException:not valid
Java Exception propagation
An exception is first thrown from the top of the stack and if it is not caught, it drops down the
call stack to the previous method,If not caught there, the exception again drops down to the
previous method, and so on until they are caught or until they reach the very bottom of the call
stack.This is called exception propagation.
Rule: By default Unchecked Exceptions are forwarded in calling chain (propagated).
Program of Exception Propagation
1.

class TestExceptionPropagation1{
void m(){
int data=50/0;
}
void n(){
m();
}
void p(){
try{
n();
}catch(Exception e)
{
System.out.println("exception handled");
}
}
public static void main(String args[])
{
TestExceptionPropagation1 obj=new TestExceptionPropagation1();
obj.p();
System.out.println("normal flow...");
}
}

Output:
exception handled
normal flow...

In the above example exception occurs in m() method where it is not handled,so it is propagated
to previous n() method where it is not handled, again it is propagated to p() method where
exception is handled.
Exception can be handled in any method in call stack either in main() method,p() method,n()
method or m() method.

Rule: By default, Checked Exceptions are not forwarded in calling chain (propagated).
Program which describes that checked exceptions are not propagated
class TestExceptionPropagation2
{
void m()
{
throw new java.io.IOException("device error");//checked exception
}
void n()
{
m();
}
void p()
{
try{
n();

}
catch(Exception e)
{
System.out.println("exception handeled");
}
}
public static void main(String args[])
{
TestExceptionPropagation2 obj=new TestExceptionPropagation2();
obj.p();
System.out.println("normal flow");
}
}
Output:Compile Time Error
Java throws keyword
The Java throws keyword is used to declare an exception. It gives an information to the
programmer that there may occur an exception so it is better for the programmer to provide the
exception handling code so that normal flow can be maintained.
Exception Handling is mainly used to handle the checked exceptions. If there occurs any
unchecked exception such as NullPointerException, it is programmers fault that he is not
performing check up before the code being used.
Syntax of java throws

return_type method_name() throws exception_class_name


{
//method code
}

Which exception should be declared


Ans) checked exception only, because:
o
o

unchecked Exception: under your control so correct your code.


error: beyond your control e.g. you are unable to do anything if there occurs
VirtualMachineError or StackOverflowError.

Advantage of Java throws keyword


Now Checked Exception can be propagated (forwarded in call stack).
It provides information to the caller of the method about the exception.

Java throws example


Let's see the example of java throws clause which describes that checked exceptions can be
propagated by throws keyword.

import java.io.IOException;
class Testthrows1
{
void m()throws IOException
{
throw new IOException("device error");//checked exception
}
void n()throws IOException
{
m();
}
void p()
{
try{
n();
}
catch(Exception e)
{
System.out.println("exception handled");
}
}
public static void main(String args[])
{
Testthrows1 obj=new Testthrows1();
obj.p();
System.out.println("normal flow...");
}
}

Output:
exception handled
normal flow...

Rule: If you are calling a method that declares an exception, you must either caught or declare
the exception.
There are two cases:
1. Case1:You caught the exception i.e. handle the exception using try/catch.
2. Case2:You declare the exception i.e. specifying throws with the method.
Case1: You handle the exception
o

In case you handle the exception, the code will be executed fine whether exception occurs
during the program or not.

import java.io.*;
class M{
void method()throws IOException
{
throw new IOException("device error");
}
}
public class Testthrows2
{
public static void main(String args[])
{
try{
M m=new M();
m.method();
}
catch(Exception e){System.out.println("exception handled");
}
System.out.println("normal flow...");
}
}
Output:
exception handled
normal flow...

Case2: You declare the exception


o
o

A)In case you declare the exception, if exception does not occur, the code will be executed
fine.
B)In case you declare the exception if exception occures, an exception will be thrown at
runtime because throws does not handle the exception.

A)Program if exception does not occur


import java.io.*;
class M{
void method()throws IOException
{
System.out.println("device operation performed");
}
}
class Testthrows3{
public static void main(String args[])throws IOException
{//declare exception
M m=new M();
m.method();
System.out.println("normal flow...");
}
}
Output:
device operation performed
normal flow...
B)Program if exception occurs
import java.io.*;
class M
{
void method()throws IOException
{
throw new IOException("device error");
}
}
class Testthrows4
{

public static void main(String args[])throws IOException{//declare exception


M m=new M();
m.method();
System.out.println("normal flow...");
}
}
Output:
Runtime Exception

Difference between throw and throws in Java


There are many differences between throw and throws keywords. A list of differences between
throw and throws are given below:
No. throw

throws

1)

Java throw keyword is used to


explicitly throw an exception.

Java throws keyword is used to


declare an exception.

2)

Checked exception cannot be


propagated using throw only.

Checked exception can be


propagated with throws.

3)

Throw is followed by an instance.

Throws is followed by class.

4)

Throw is used within the method.

Throws is used with the method


signature.

5)

You cannot throw multiple


exceptions.

You can declare multiple


exceptions e.g.
public void method()throws
IOException,SQLException.

Java throw example


void m()
{
throw new ArithmeticException("sorry");
}
Java throws example
void m()throws ArithmeticException
{

//method code
}
Java throw and throws example
void m()throws ArithmeticException
{
throw new ArithmeticException("sorry");
}

Can we rethrow an exception?


Yes, by throwing same exception in catch block.

User defined Exception subclass


You can also create your own exception sub class simply by extending java Exception class. You can define a
constructor for your Exception sub class (not compulsory) and you can override the toString() function to
display your customized message on catch.

User defined exceptions in java are also known as Custom exceptions. Most of the times when
we are developing an application in java, we often feel a need to create and throw our own
exceptions. These exceptions are known as User defined or Custom exceptions
class MyException extends Exception
{
private int ex;
MyException(int a)
{
ex=a;
}
public String toString()
{
return "MyException[" + ex +"] is less than zero";
}
}

class Test
{
static void sum(int a,int b) throws MyException
{
if(a<0)
{
throw new MyException(a);
}
else
{
System.out.println(a+b);
}
}
public static void main(String[] args)
{
try
{
sum(-10, 10);
}
catch(MyException me)
{
System.out.println(me);
}
}
}

Points to Remember
1.

Extend the Exception class to create your own ecxeption class.

2.

You don't have to implement anything inside it, no methods are required.

3.

You can have a Constructor if you want.

4.

You can override the toString() function, to display customized message.

Example of User defined exception in Java


class MyException extends Exception
{
String str1;
MyException(String str2)
{
str1=str2;
}
public String toString()
{

return ("Output String = "+str1) ;


}
}
class CustomException
{
public static void main(String args[])
{
try
{
throw new MyException("Custom");
// I'm throwing user defined custom exception above
}
catch(MyException exp)
{
System.out.println("Hi this is my catch block") ;
System.out.println(exp) ;
}
}
}
Output:
Hi this is my catch block
Output String = Custom

Another example wherein we will modify the error message


of Exception Class
Part 1: I have created my own exception class- MyException by inheriting the parent
class Exception then I have defined a parametric constructor of my class with a String
parameter. In the constructor I called super(), super refers to the super class { My class has
inherited Exception class so Exception class is my superclass}. In this way I have modified the
system generated message by my own message.
public class MyException extends Exception
{
public MyException(String mymsg)
{
super(mymsg);
}
}

Part2:

public class ExceptionSample


{
public static void main(String args[]) throws Exception
{
ExceptionSample es = new ExceptionSample();
es.displayMymsg();
}
public void displayMymsg() throws MyException
{
for(int j=8;j>0;j--)
{
System.out.println("j= "+j);
if(j==7)
{
throw new MyException("This is my own Custom Message");
}
}
}
}
Output:
j=8
j=7
Exception in thread "main" MyException: This is my own Custom Message
at ExceptionSample.displayMymsg( ExceptionSample.java.19)
...

User-defined Exceptions
You can create your own exceptions in Java. Keep the following points in mind when
writing your own exception classes

All exceptions must be a child of Throwable.

If you want to write a checked exception that is automatically enforced by the Handle or
Declare Rule, you need to extend the Exception class.

If you want to write a runtime exception, you need to extend the RuntimeException class.

We can define our own Exception class as below


class MyException extends Exception {
}
You just need to extend the predefined Exception class to create your own
Exception.
These
are
considered
to
be
checked
exceptions.
The
followingInsufficientFundsException class is a user-defined exception that
extends the Exception class, making it a checked exception. An exception class is
like any other class, containing useful fields and methods.

Example
// File Name InsufficientFundsException.java
import java.io.*;
public class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(double amount) {
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
To demonstrate using our user-defined exception, the following CheckingAccount
class contains a withdraw() method that throws an InsufficientFundsException.
// File Name CheckingAccount.java
import java.io.*;
public class CheckingAccount {
private double balance;
private int number;
public CheckingAccount(int number) {
this.number = number;
}
public void deposit(double amount) {
balance += amount;

}
public void withdraw(double amount) throws InsufficientFundsException {
if(amount <= balance) {
balance -= amount;
}else {
double needs = amount - balance;
throw new InsufficientFundsException(needs);
}
}
public double getBalance() {
return balance;
}
public int getNumber() {
return number;
}
}
The following BankDemo program demonstrates invoking the deposit() and withdraw() methods
of CheckingAccount.
// File Name BankDemo.java
public class BankDemo {
public static void main(String [] args) {
CheckingAccount c = new CheckingAccount(101);
System.out.println("Depositing $500...");
c.deposit(500.00);
try {
System.out.println("\nWithdrawing $100...");

c.withdraw(100.00);
System.out.println("\nWithdrawing $600...");
c.withdraw(600.00);
}catch(InsufficientFundsException e) {
System.out.println("Sorry, but you are short $" + e.getAmount());
e.printStackTrace();
}
}
}
Compile all the above three files and run BankDemo. This will produce the following
result

Output
Depositing $500...
Withdrawing $100...
Withdrawing $600...
Sorry, but you are short $200.0
InsufficientFundsException
at CheckingAccount.withdraw(CheckingAccount.java:25)
at BankDemo.main(BankDemo.java:13)

Common Exceptions
In Java, it is possible to define two catergories of Exceptions and Errors.

JVM Exceptions These are exceptions/errors that are exclusively or logically thrown by
the

JVM.

Examples:

NullPointerException,

ArrayIndexOutOfBoundsException,

ClassCastException.

Programmatic Exceptions These exceptions are thrown explicitly by the application or


the API programmers. Examples: IllegalArgumentException, IllegalStateException.

Example
Client.java
public class Client
{
public static void main(String[] args)throws Exception
{
int price = -120;
if(price < 0)
throw new MyOwnExceptionClass(price);
else
System.out.println("Your age is :"+price);
}
}

MyOwnExceptionClass.java
public class MyOwnExceptionClass extends Exception {
private int price;
public MyOwnExceptionClass(int price){
this.price = price;
}
public String toString(){
return "Price should not be in negative, you are entered" +price;
}
}

Example
class JavaException{
public static void main(String args[]){
try{
throw new MyException(2);
// throw is used to create a new exception and throw it.
}

catch(MyException e){
System.out.println(e) ;
}
}
}
class MyException extends Exception{
int a;
MyException(int b) {
a=b;
}
public String toString(){
return ("Exception Number = "+a) ;
}
}

Example
public class UserDefinedExceptionExample {
public static void main(String[] args) throws Exception {
try {
/**
* Throw user defined exception
*/
throw new UserDefinedException();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* OUTPUT:
*
* UserDefinedException
* at UserDefinedExceptionExample.main(UserDefinedExceptionExample.java:16)
*/
/**
* To create user defined exception
* one step is enough
* that is just to extend Exception class
*/
public class UserDefinedException extends Exception {
}
s

You might also like