내부 클래스
- 내부 클래스는 클래스 내부에 선언한 클래스이다.
- 내부 클래스는 선언하는 위치나 예약어에 따라 크게 네 가지 유형으로 나누어진다.
- 인스턴스 내부 클래스, 정적 내부 클래스, 지역 내부 클래스는 클래스 내부의 선언하는 변수의 유형과 유사하다.
- 익명 내부 클래스는 클래스 이름 없이 선언하고 바로 사용이 가능하다.
인스턴스 내부 클래스
- 인스턴스 변수를 선언할 때와 같은 위치에 선언하며, 외부 클래스 내부에서만 생성하여 사용하는 객체를
선언할 때 쓴다. - 인스턴스 내부 클래스는 외부 클래스가 먼저 생성되어야 생성 가능하다.
package innerclass;
class OutClass { //외부 클래스
private int num = 10; //외부 클래스 private 변수
private static int sNum = 20; //외부 클래스 정적 변수
private InClass inClass; //내부 클래스 자료형 변수를 먼저 선언
public OutClass(){ //외부 클래스 디폴트 생성자
inClass = new InClass(); // 내부 클래스 생성
}
class InClass{ //인스턴스 내부 클래스
int inNum = 100;
//static int sInNum = 200; //인스턴스 내부 클래스에 정적 변수 선언 불가능
void inTest(){
System.out.println("OutClass num = " +num + "(외부 클래스의 인스턴스 변수)");
System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 스태틱 변수)");
}
//static void sTest(){ //정적 메서드 역시 정의 불가능
//}
}
public void usingClass(){
inClass.inTest(); //내부 클래스 변수를 사용하여 메서드 호출하기
}
public class InnerTest{
public static void main(String[] args){
OutClass outClass = new OutClass();
System.out.println("외부 클래스 이용하여 내부 클래스 기능 호출");
outClass.usingClass(); // 내부 클래스 기능 호출
}
}
정적 내부 클래스
- 내부 클래스가 외부 클래스 생성과 무관하게 사용할수 있어야 하고 정적 변수도 사용할 수 있어야 할 때 사용한다.
- static 예약어를 함께 사용한다.
- 정적 내부 클래스는 외부 클래스를 생성하지 않고도 내부 클래스 자료형으로 바로 선언하여 생성할 수 있다.
package innerclass;
class OutClass { //외부 클래스
private int num = 10; //외부 클래스 private 변수
private static int sNum = 20; //외부 클래스 정적 변수
static class InStaticClass{ //정적 내부 클래스
int inNum = 100; //정적 내부 클래스의 인스턴스 변수
static int sInNum = 200; //정적 내부 클래스의 정적 변수
void inTest(){ //정적 클래스의 일반 메서드
//num += 10; // 외부 클래스의 인스턴스 변수는 사용할 수 없음.
System.out.println("InStaticClass inNum = " + inNum + "(내부 클래스의 인스턴스 변수 사용)");
System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 스태틱 변수 사용)");
System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 스태틱 변수 사용)");
}
static void sTest(){ // 정적 내부 클래스의 static 메서드
//num += 10; // 외부 클래스의 인스턴스 변수는 사용할 수 없음.
//inNum += 10; // 내부 클래스의 인스턴스 변수는 사용할 수 없음
System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 스태틱 변수 사용)");
System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 스태틱 변수 사용)");
}
}
}
public class InnerTest{
public static void main(String[] args){
OutClass.InClass inClass = outClass.new InClass(); // 외부 클래스를 이용하여 내부 클래스 생성
System.out.println("외부 클래스 변수를 이용하여 내부 클래스 생성");
inClass.inTest();
System.out.println();
//외부 클래스 생성하지 않고 바로 정적 내부 클래스 생성
OutClass.InStaticClass sInClass = new OutClass.InStaticClass();
System.out.println("정적 내부 클래스 일반 메서드 호출");
sInClass.inTest();
System.out.println();
System.out.println("정적 내부 클래스의 스태틱 메소드 호출");
OutClass.InStaticClass.sTest();
}
}
지역 내부 클래스
- 지역 변수처럼 메서드 내부에 클래스를 정의하여 사용하는 것을 말한다. -> 메서드 안에서만 사용 가능
package innerclass;
class Outer{
int outNum = 100;
static int sNum = 200;
Runnable getRunnable(int i){
int num = 100; //지역 변수
class MyRunnable implements Runnable{ //지역 내부 클래스
int localNum = 10; //지역 내부 클래스 인스턴스 변수
@Override
public void run() {
//num = 200; //지역 변수는 상수로 바뀌므로 값을 변경할 수 없어 오류 발생
//i = 100; //매개변수도 지역 변수처럼 상수로 바뀌므로 값을 변경할 수 없어 오류 발생
System.out.println("i =" + i);
System.out.println("num = " +num);
System.out.println("localNum = " +localNum);
System.out.println("outNum = " + outNum + "(외부 클래스 인스턴스 변수)");
System.out.println("Outter.sNum = " + Outer.sNum + "(외부 클래스 정적 변수)");
}
}
return new MyRunnable();
}
}
public class LocalInnerTest {
public static void main(String[] args) {
Outer out = new Outer();
Runnable runner = out.getRunnable(10); //메서드 호출
runner.run();
}
}
익명 내부 클래스
- 클래스 이름을 사용하지 않는 클래스이다.
package innerclass;
class Outter2{
Runnable getRunnable(int i){
int num = 100;
//MyRunnable 클래스 이름을 빼고 클래스를 바로 생성하는 방법
return new Runnable() { //익명 내부 클래스, Runnable 인터페이스 생성
@Override
public void run() {
//num = 200; //에러 남
//i = 10; //에러 남
System.out.println(i);
System.out.println(num);
}
}; //클래스 끝에 ;를 붙임
}
//인터페이스나 추상 클래스형 변수를 선언하고 클래스를 생성해 대입하는 방법
Runnable runner = new Runnable() { //익명 내부 클래스를 변수에 대입
@Override
public void run() {
System.out.println("Runnable 이 구현된 익명 클래스 변수");
}
};
}
public class AnonymousInnerTest {
public static void main(String[] args) {
Outter2 out = new Outter2();
Runnable runnerble = out.getRunnable(10);
runnerble.run();
out.runner.run();
}
}
람다식
- 프로그래밍 언어 중에는 함수의 구현과 호출만으로 프로그램을 만들 수 있는 프로그래밍 방식이 있는데 이를
함수형 프로그래밍이라 하고, 자바에서 제공하는 함수형 프로그래밍을 람다식이라고 한다. - 람다식은 함수 이름이 없는 익명 함수를 만든다고 생각하면 된다.
- (매개변수) -> { 실행문; }
문법 살펴보기
- 람다식 문법에서는 매개변수 자료형을 생략할 수도 있고, 하나인 경우에는 괄호도 생략 가능하다.
- str -> {System.out.println(str);}
- 중괄호 안의 구현 부분이 한 문장인 경우 중괄호를 생략할 수 있다.
- str -> System.out.println(str);
- 하지만 return문의 경우 한 문장이더라도 생략 불가능 하다.
- 중괄호 안의 구현 부분이 return문 하나라면 중괄호와 return 모두 생략하고 식만 쓸 수 있다.
- (x, y) -> x + y
str -> str.length()
- (x, y) -> x + y
람다식 사용하기
- 함수형 인터페이스 선언하기
package lambda;
@FunctionalInterface
public interface MyNumber {
int getMax(int num1, int num2); //추상 메서드 선언
}
- 람다식 구현과 호출
package lambda;
public class TestMyNumber {
public static void main(String[] args) {
MyNumber max = (x, y)->(x>= y)? x:y; // 람다식을 인터페이스 자료형 max 변수에 대입
System.out.println(max.getMax(10, 20));// 인터페이스 자료형 변수로 함수 호출
// MyNumber max2 = (x, y)-> x>=y ?x:y;
// System.out.println(max2.getMax(10, 20));
}
}
객체 지향 프로그래밍 방식과 람다식 비교
- 인터페이스 구현하기
package lambda;
public interface StringConcat {
public void makeString(String s1, String s2);
}
클래스에서 인터페이스 구현하기
- 추상 메서드 구현하기
package lambda;
public class StringConCatImpl implements StringConcat{
@Override
public void makeString(String s1, String s2) {
System.out.println( s1 + "," + s2 );
}
}
- 메서드 테스트하기
package lambda;
public class TestStringConcat {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "World";
StringConCatImpl concat1 = new StringConCatImpl();
concat1.makeString(s1, s2);
}
makeString() 메서드를 수행하려면 StringConcat 인터페이스를 구현한 StringConCatImpl 클래스를 인스턴스로
생성해야 한다.
람다식으로 인터페이스 구현하기
- 람다식으로 인터페이스 구현하기
package lambda;
public class TestStringConcat {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "World";
StringConcat concat2 = (s, v)->System.out.println(s + "," + v ); //System.out.println(i);
concat2.makeString(s1, s2);
}
두 매개변수 s, v를 사용해 연결된 문자열을 출력하도록 구현하였는데, 이 부분은 concat2에 대입하고 변수를 사용하여
makeString() 메서드를 호출했다. 람다식으로 구현하는 경우 코드가 더 간결해 지는 것을 알 수 있다.
익명 객체를 생성하는 람다식
- 람다식으로 메서드를 구현해서 호출하면 컴퓨터 내부에서는 익명 클래스가 생성되고 이를 통해 익명 객체가
생성되게 되는 것이다. - 람다식을 사용하면 익명 내부 클래스가 생성되기 때문에 람다식 내부에서 변수를 변경하면 오류가 발생한다.
함수를 변수처럼 사용하는 람다식
- 람다식을 이용하면 구현된 함수를 변수처럼 사용할 수 있다.
인스턴스형 변수에 람다식 대입하기
- 함수형 인터페이스에 메서드를 하나 선언하고 이 메서드를 람다식으로 구현한다.
- 실행하기 위해 인터페이스형 변수를 선언하고 람다식 구현부를 대입
interface PrintString {
void showString(String str);
}
PrintString lambdaStr = s -> System.out.println(s);
lambdaStr.showString("Hello Lambda");
매개변수로 전달하는 람다식
- 람다식을 변수에 대입하면 이를 매개변수로 전달할 수 있다. 이 때 전달되는 매개변수는 인터페이스형이다.
package lambda;
interface PrintString{
void showString(String str);
}
public class TestLambda {
public static void main(String[] args) {
PrintString lambdaStr = s->System.out.println(s); //람다식을 인터페이스형 변수에 대입, 그 변수를 사용해 람다식 구현부 호출
lambdaStr.showString("hello lambda_1");
showMyString(lambdaStr); //메서드의 매개변수로 람다식을 대입한 변수 전달
}
public static void showMyString(PrintString p) { //매개변수를 인터페이스형으로 받음
p.showString("hello lambda_2");
}
}
반환 값으로 쓰이는 람다식
- 메서드의 반환형을 람다식의 인터페이스형으로 선언하면 구현한 람다식을 반환할 수 있다.
package lambda;
interface PrintString{
void showString(String str);
}
public class TestLambda {
public static void main(String[] args) {
PrintString lambdaStr = s->System.out.println(s); //람다식을 인터페이스형 변수에 대입, 그 변수를 사용해 람다식 구현부 호출
lambdaStr.showString("hello lambda_1");
showMyString(lambdaStr); //메서드의 매개변수로 람다식을 대입한 변수 전달
PrintString reStr = returnString(); //변수로 반환받기
reStr.showString("hello "); //메서드 호출
}
public static void showMyString(PrintString p) { //매개변수를 인터페이스형으로 받음
p.showString("hello lambda_2");
}
public static PrintString returnString() { //반환 값으로 사용
return s->System.out.println(s + "world"); //람다식을 반환하는 메서드
}
}
스트림
- 여러 자료의 처리에 대한 기능을 구현해 놓은 클래스를 스트림이라 한다.
- 스트림을 활용하면 배열, 컬렉션 등의 자료를 일관서 있게 처리할 수 있다.
스트림 연산
- 크게 중간 연산과 최종 연산 두 가지가 있다.
- 중간 연산은 자료를 거르거나 변경하여 또 다른 자료를 내부적으로 생성한다.
- filter() : 조건을 넣고 그 조건에 맞는 참인 경우만 추출하는 경우에 사용
- map() : 클래스가 가진 자료 중 이름만 출력하는 겨웅에 사용
- 최종 연산은 생성된 내부 자료를 소모해 가면서 연산을 수행하기 때문에 마지막에 한 번만 호출된다.
- forEach() : 요소를 하나씩 꺼내는 기능을 수행
- sum(), count() : 배열 요소의 합계나 개수를 출력하는 연산을 수행
스트림 생성하고 사용하기
- 정수 배열에 스트림 생성하고 사용하기
package stream;
import java.util.Arrays;
public class IntArrayTest {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
int sumVal = Arrays.stream(arr).sum(); //sum() 연산으로 arr 배열에 저장된 값을 모두 더함
int count = (int) Arrays.stream(arr).count(); //count() 연산으로 arr 배열의 요소 개수를 반환함
System.out.println(sumVal);
System.out.println(count);
}
}
- ArrayList에서 스트림 활용하기
package stream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class ArrayListStreamTest {
public static void main(String[] args) {
List<String> sList = new ArrayList<String>();
sList.add("Tomas");
sList.add("Edward");
sList.add("Jack");
Stream<String> stream = sList.stream(); //스트림 생성
stream.forEach(s->System.out.print(s + " ")); //배열의 요소를 하나씩 출력
System.out.println();
sList.stream().sorted().forEach(s->System.out.print(s+ " "));
//스트림 새로 생성.정렬.요소를 하나씩 꺼내어 출력
}
}
스트림의 특징
- 자료의 대상과 관계없이 동일한 연산을 수행한다.
- 한 번 생성하고 사용한 스트림은 재사용할 수 없다.
- 스트림의 연산은 기존 자료를 변경하지 않는다.
- 스트림의 연산은 중간 연산과 최종 연산이 있다.
프로그래머가 기능을 지정하는 reduce() 연산
- reduce() 연산은 내부적으로 스트림의 요소를 하나씩 소모하면서 프로그래머가 직접 지정한 기능을 수행한다.
package stream;
import java.util.Arrays;
import java.util.function.BinaryOperator;
class CompareString implements BinaryOperator<String>{ //BinaryOperator를 구현한 클래스 정의
@Override
public String apply(String s1, String s2) { //reduce() 메서드가 호출될 때 불리는 메서드
//두 문자열 길이를 비교
if (s1.getBytes().length >= s2.getBytes().length) return s1;
else return s2;
}
}
public class ReduceTest {
public static void main(String[] args) {
String[] greetings = {"안녕하세요~~~", "hello", "Good morning", "반갑습니다^^"};
//람다식을 직접 구현하는 방법
System.out.println(Arrays.stream(greetings).reduce("", (s1, s2)->
{if (s1.getBytes().length >= s2.getBytes().length)
return s1;
else return s2;}));
String str = Arrays.stream(greetings).reduce(new CompareString()).get(); //BinaryOperator를 구현한 클래스 이용
System.out.println(str);
}
}
'Java-Study' 카테고리의 다른 글
Java Study : 10주차 정리 - 예외처리 (0) | 2023.01.14 |
---|---|
Java Study : 9주차 - 백준 문제풀이 (0) | 2023.01.08 |
Java Study : 8주차 - 백준 문제풀이 (0) | 2022.12.23 |
Java Study : 8주차 정리 - 컬렉션 프레임워크(제네릭, 컬렉션 프레임워크, (List, Set, Map)인터페이스) (0) | 2022.12.23 |
Java Study : 7주차 - 백준 문제풀이 (0) | 2022.12.23 |