*본 내용은 [Do it! 자바 프로그래밍 입문] 책과 강의를 보고 공부하면서 요점 정리한 내용입니다.
종류 | 구현 위치 | 사용할 수 있는 외부 클래스 변수 | 생성 방법 |
---|---|---|---|
인스턴스 내부 클래스 | 외부 클래스 멤버 변수와 동일 | 외부 인스턴스 변수 외부 전역 변수 | 외부 클래스를 먼저 만든 후 내부 클래스 생성 |
정적 내부 클래스 | 외부 클래스 멤버 변수와 동일 | 외부 전역 변수 | 외부 클래스와 무관하게 생성 |
지역 내부 클래스 | 메서드 내부에 구현 | 외부 인스턴스 변수 외부 전역 변수 | 메서드를 호출할 때 생성 |
익명 내부 클래스 | 메서드 내부에 구현 변수에 대입하여 직접 구현 | 외부 이스턴스 변수 외부 전역 변수 | 메서드를 호출할 때 생성되거나, 인터페이스 타입 변수에 대입할 때 new 예약어를 사용하여 생성 |
[인스턴스 내부 클래스, 정적 내부 클래스]
InnerTest.java
package innerclass;
class OutClass{
private int num = 10;
private static int sNum = 20;
private InClass inClass;
public OutClass() {
inClass = new InClass();
}
private class InClass{
int inNum = 200;
// static sInNum = 100;
// static void sTest() {
// }
//오류 발생. 내부 클래스에서 static 변수, static 메서드 선언할 수 없다.
void inTest() {
System.out.println(num); // 외부 클래스 변수 사용 가능
System.out.println(sNum);
}
}
public void usingInTest() {
inClass.inTest();
}
static class InStaticClass{
int iNum = 100;
static int sInNum = 200;
void inTest() {
// num += 10; 오류 발생. static 클래스이므로 인스턴스 생성과 상관 없이 쓰일 수 있어서 OutClass의 num변수를 사용하지 못함.
sNum += 10; // static변수이므로 사용 가능
System.out.println(sNum);
System.out.println(iNum); // 내부 변수들은 사용 가능
System.out.println(sInNum);
}
static void sTest() {
System.out.println(sNum);
// System.out.println(iNum); // 내부 static 함수는 내부 변수들 중 인스턴스 변수는 사용 불가능
System.out.println(sInNum);
}
}
}
public class InnerTest {
public static void main(String[] args) {
OutClass outClass = new OutClass();
//OutClass의 InClass가 private이 아닐 때 inTest()사용하기
//방법1
// outClass.inClass.inTest();
//방법2
// OutClass.InClass inClass = outClass.new InClass();
// inClass.inTest();
//OutClass의 InClass가 private일 때 inTest()사용하기
outClass.usingInTest();
System.out.println();
//내부 static 클래스 사용하기 - OutClass 인스턴스 생성과 상관 없이 사용 가능
OutClass.InStaticClass sInClass = new OutClass.InStaticClass();
sInClass.inTest();
System.out.println();
OutClass.InStaticClass.sTest(); // OutClass, sInClass 인스턴스 생성과 상관 없이 사용 가능
}
}
실행 결과
10
20
30
100
200
30
200
[지역 내부 클래스]
LocalInnerTest.java
package innerclass;
class Outer{
int outNum = 100;
static int sNum = 200;
public Runnable getRunnable() { //지역내부클래스
int localNum = 100; // 상수화(final)
class MyRunnable implements Runnable{
@Override
public void run() {
// localNum += 100; 오류 발생. localNum 변수는 final로 정의 되어 값을 바꿀 수 없다.
outNum += 10; // 지역 변수는 값 변화할 수 있음.
System.out.println(outNum);
System.out.println(sNum);
System.out.println(localNum);
}
}
return new MyRunnable();
}
}
public class LocalInnerTest {
public static void main(String[] args) {
Outer outer = new Outer();
outer.getRunnable().run();
}
}
실행 결과
110
200
100
[익명 내부 클래스]
package innerclass;
class Outer{
/***메서드 안에서 구현하기***/
Runnable getRunnable(int i){
int num = 100; // 상수화(final)
return new 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) {
Outer out = new Outer();
Runnable runnerble = out.getRunnable(10);
runnerble.run();
out.runner.run();
}
}
실행 결과
10
100
Runnable 이 구현된 익명 클래스 변수
자바에서 함수형 프로그래밍(functional programming)을 구현하는 방식
자바8부터 지원
클래스를 생성하지 않고 함수의 호출만으로 기능을 수행
함수형 프로그래밍
순수 함수(pure function)를 구현하고 호출함으로써 외부 자료에 부수적인 영향을 주지 않고 매개 변수만을 사용하도록 만든 함수
함수를 기반으로 구현
입력 받은 자료를 기반으로 수행되고 외부에 영향을 미치지 않으므로 병렬처리등에 가능
안정적인 확장성있는 프로그래밍 방식
(매개변수) -> {실행문;}
int add(int x, int y){
return x + y;
}
↓
(int x, int y) -> {return x + y;}
str -> {System.out.println(str);}
x, y -> {System.out.println(x + y);} // 잘못된 형식
str -> System.out.println(str);
str -> return str.length(); // 잘못된 형식
(x, y) -> x + y // 두 값을 더하여 반환함 (return, 중괄호 생략)
str -> str.length() // 문자열의 길이를 반환함
람다식을 선언하기 위한 인터페이스
익명 함수와 매개 변수만으로 구현되므로 단 하나의 메서드만을 가져야함
(두 개 이상의 메서드인 경우 어떤 메서드의 호출인지 모호해 짐)
@FunctionalInterface 애노테이션
함수형 인터페이스라는 의미, 여러 개의 메서드를 선언하면 에러남
MyNumber.java
package lambda;
@FunctionalInterface
public interface MyNumber {
int getMaxNumber(int num1, int num2);
// int testNumber(int x, int y); // 메서드 여러 개 있으면 어떤 메서드 말하는 건지 모호해짐 --> 메서드 하나만 선언하고 에너테이션 추가
}
TestMyNumber.java
package lambda;
public class TestMyNumber {
public static void main(String[] args) {
MyNumber maxNum = (x, y) -> (x >= y)? x : y;
int max = maxNum.getMaxNumber(10, 20);
System.out.println(max);
}
}
실행 결과
20
자바는 객체 지향 언어로 객체를 생성해야 메서드가 호출됨
람다식으로 메서드를 구현하고 호출하면 내부에서 익명 클래스가 생성됨
StringConcat concat3 = new StringConcat() {
@Override
public void makeString(String s1, String s2) {
System.out.println(s1 + "," + s2);
}
};
StringConcat.java
package lambda;
@FunctionalInterface
public interface StringConcat {
public void makeString(String s1, String s2);
}
StringConcatImpl.java
package lambda;
public class StringConcatImpl implements StringConcat{
@Override
public void makeString(String s1, String s2) {
System.out.println(s1 + " " + s2);
}
}
TestStringConcat.java
package lambda;
public class TestStringConcat {
public static void main(String[] args) {
//객체 지향 방식으로 구현하기 - 여러 개 인터페이스 여러 개 메서드 가능
StringConcatImpl sImpl = new StringConcatImpl();
sImpl.makeString("hello", "java");
//람다식으로 구현하기 - 하나의 메서드만
StringConcat concat = (s1, s2)->System.out.println(s1 + " " + s2);
concat.makeString("hello", "java");
//익명 내부 클래스로 구현하기 - 하나의 인터페이스나 하나의 추상클래스만
StringConcat concat2 = new StringConcat() {
@Override
public void makeString(String s1, String s2) {
System.out.println(s1 + " " + s2);
}
};
concat2.makeString("hello", "java");
}
}
실행 결과
hello java
hello java
hello java
변수를 사용하는 경우 | 예시 |
---|---|
특정 자료형으로 변수 선언 후 값 대입하여 사용하기 | int a = 10; |
매개변수로 전달하기 | int add(int x, int y); |
메서드의 반환 값으로 반환하기 | return num; |
interface PrintString {
void showString(String str);
}
↓
s -> System.out.println(s)
// 인터페이스형 변수에 람다식 대입
PrintString lambdaStr = s -> System.out.println(s);
lambdaStr.showString("hello lambda_1");
package lambda;
interface PrintString{
void showString(String str);
}
public class LambdaTest {
public static void main(String[] args) {
PrintString lambdaPrint = str->System.out.println(str);
lambdaPrint.showString("test");
showMyString(lambdaPrint);
}
public static void showMyString(PrintString lambda) {
lambda.showString("test2");
}
}
실행 결과
test
test2
package lambda;
interface PrintString{
void showString(String str);
}
public class LambdaTest {
public static void main(String[] args) {
PrintString lambdaPrint = str->System.out.println(str);
lambdaPrint.showString("test");
PrintString reStr = returnPrint();
reStr.showString("hello");
}
public static PrintString returnPrint() {
return s->System.out.println(s + " world");
}
}
실행 결과
test
hello world
※ 15장의 입출력을 위한 스트림과는 별개다.
자료의 대상과 관계 없이 동일한 연산을 수행
∙ 배열, 컬렉션을 대상으로 동일한 연산을 수행 함
∙ 일관성 있는 연산으로 자료의 처리를 쉽고 간단하게 함
한 번 생성하고 사용한 스트림은 재사용할 수 없음
∙ 자료에 대한 스트림을 생성하여 연산을 수행하면 스트림은 소모됨
∙ 다른 연산을 위해서는 새로운 스트림을 생성함
스트림 연산은 기존 자료를 변경하지 않음
∙ 자료에 대한 스트림을 생성하면 별도의 메모리 공간을 사용하므로 기존 자료를 변경하지 않음
스트림 연산은 중간 연산과 최종 연산으로 구분 됨
∙ 스트림에 대해 중간 연산은 여러 개 적용될 수 있지만 최종 연산은 마지막에 한 번만 적용됨
∙ 최종연산이 호출되어야 중간연산의 결과가 모두 적용됨. 이를 '지연 연산'이라 함
중간 연산 - filter(), map()
조건에 맞는 요소를 추출(filter())하거나 요소를 변환 함(map())
문자열의 길이가 5 이상인 요소만 출력하기
고객 클래스에서 고객 이름만 가져오기
IntArrayTest.java
package stream;
import java.util.Arrays;
import java.util.stream.IntStream;
public class IntArrayTest {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, };
// 한 번 스트림을 생성하고 (Arrays.stream(arr)부분) 사용하면 다시 사용할 수 없음
IntStream stream = Arrays.stream(arr);
int sum = stream.sum(); // 스트림 사용
System.out.println(sum);
// int count = (int)stream.count(); // 오류 발생
int count = (int)Arrays.stream(arr).count(); // 스트림 다시 생성한 뒤 사용
System.out.println(count);
}
}
실행 결과
15
5
ArrayListTest.java
package stream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class ArrayListTest {
public static void main(String[] args) {
List<String> sList = new ArrayList<String>();
sList.add("Tomas");
sList.add("James");
sList.add("Edward");
Stream<String> stream = sList.stream();
stream.forEach(s->System.out.println(s)); // 스트림 소모
System.out.println();
//위와 동일
for(String s : sList) {
System.out.println(s);
}
System.out.println();
sList.stream().sorted().forEach(s->System.out.println(s)); // 오름차순 기본
}
}
실행 결과
Tomas
James
Edward
Tomas
James
Edward
Edward
James
Tomas
정의된 연산이 아닌 프로그래머가 직접 지정하는 연산을 적용
최종 연산으로 스트림의 요소를 소모하여 연산 수행
배열의 모든 요소의 합을 구하는 reduce()연산
Arrays.stream(arr).reduce(초깃값, (전달되는 요소) -> 각 요소가 수행해야 할 기능));
Arrays.stream(arr).reduce(0, (a, b) -> a + b));
ReduceTest.java
package stream;
import java.util.Arrays;
import java.util.function.BinaryOperator;
class CompareString implements BinaryOperator<String>{
@Override
public String apply(String s1, String s2) {
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", "반갑습니다"};
//가장 긴 문장 출력하기
//람다식 reduce() 이용 방법 1
System.out.println(Arrays.stream(greetings).reduce("", (s1, s2) -> {
if(s1.getBytes().length >= s2.getBytes().length)
return s1;
else return s2;}
));
//람다식 reduce() 이용 방법 2
String str = Arrays.stream(greetings).reduce(new CompareString()).get();
System.out.println(str);
}
}
실행 결과
안녕하세요~~~~~~~~
안녕하세요~~~~~~~~