자바수업 8일차

하파타카·2021년 12월 13일
0

JAVA수업

목록 보기
8/10
post-thumbnail

한 일

  • 타이머 사용
  • 버튼 클릭 이벤트
  • 버튼 클릭 시 배경색 바꾸기
  • 레퍼런스 변수
  • 해시코드와 this
  • 메소드 로컬 이너 클래스
  • static과 이너 클래스 비교
  • 익명 클래스

타이머 사용

1) 추상 클래스인 TimerTask 를 상속

  • 추상클래스 TimerTask 를 상속받아 해당 클래스에서 구현(메소드 생성)
  • 이후 구현한 클래스를 사용하기 위해 Timer 객체를 생성
  • 객체명.scheduleAtFixedRate(실행할 메소드, 시작시점, 실행주기);

예) (new Task(), 0, 1000) => new Task()객체를 시작한 후 0초부터 1초에 한 번 실행
시작시점과 실행주기는 1/1000초 기준으로, 1000 = 1초이다.

package timer_Example;
import java.util.Timer;
import java.util.TimerTask;

class Task extends TimerTask {		// TimerTask클래스는 추상 클래스이므로 그걸 상속받은 Task에서 구현
	public void run() {
		System.out.println("헬로우");	
	}	
}

public class App {
	public static void main(String[] args) {
		// 타이머 예제
		Timer timer = new Timer();
		timer.scheduleAtFixedRate(new Task(), 0, 1000);	// ms기준이므로 1000이면 1초에 한번을 의미
		// (new Task(), 0, 1000) : 0초부터 시작해 1000ms(= 1초)에 한 번씩 new Task()를 실행한다
	}
}

0초부터 시작해 1초에 한 번 "헬로우"를 출력함.

2) 인터페이스 Runnable 을 구현

  • 인터페이스 Runnable을 구현하여 완성.
  • 이후 구현한 클래스를 사용하기 위해
    ScheduledExecutorService 객체명 = Executors.newScheduledThreadPool(1);
    으로 객체생성.
  • 객체 생성 후
    객체명.scheduleAtFixedRate(new Task(), 시작시점, 실행주기, 초분시등 시간기준); 으로 실행.

예) (new Task(), 0, 1, TimeUnit.SECONDS) => new Task()객체를, 실행 0초부터, 1초에 한번, 초단위 기준으로 실행.

package timer_Example2;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

class Task implements Runnable {	// Runnable 인터페이스를 구현
	@Override
	public void run() {	// Runnable의 추상메소드를 구현(완성)한다
		// 할 일의 코드를 적는다
		System.out.println("헬로헬로");
	}
}

public class App {
	public static void main(String[] args) {
		// 타이머 2
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
		executor.scheduleAtFixedRate(new Task(), 0, 1, TimeUnit.SECONDS);	// 바로시작, 1초 주기로 실행
	}
}

0초부터 시작해 1초에 한 번씩 "헬로헬로"를 출력함.

※ 추상 클래스 TimerTask 와 인터페이스 Runnable은 java에 미리 내장되어있어 만들어 줄 필요없이 상속 혹은 구현하여 사용하면 된다.

3) Timer 추상 클래스, 인터페이스 를 같이 사용

class Task1 implements Runnable {	// Runnable 인터페이스를 구현
	@Override
	public void run() {	// Runnable의 추상메소드를 구현(완성)한다
		System.out.println("타이머2 헬로");	
	}
}
class Task2 extends TimerTask {		// TimerTask클래스는 추상 클래스이므로 그걸 상속받은 Task에서 구현
	@Override
	public void run() {
		System.out.println("타이머1 헬로우");	
	}	
}

public class App {
	public static void main(String[] args) {
		// 타이머 1
		Timer timer = new Timer();
		timer.scheduleAtFixedRate(new Task2(), 0, 1000);
		// 타이머 2
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
		executor.scheduleAtFixedRate(new Task1(), 0, 1, TimeUnit.SECONDS);	// 바로시작, 1초 주기로 실행
	}
}

1초에 한 번씩 "타이머1 헬로우", "타이머2 헬로" 가 출력됨. (동시에 실행됨)

~ 추상 클래스와 인터페이스의 개념 ~

  • 추상 클래스 : 미완성 설계도의 개념.
    슈퍼 클래스의 기능(상속)을 이용하거나 확장하기 위해 활용됨.
    일반변수와 메소드를 활용할 수 있다.
    일반 메소드는 상속받은 클래스에서 다시 재정의 할 필요 없음.
    다중상속 불가.
    클래스 앞에 abstract 키워드를 사용해 생성.

  • 인터페이스 : 밑그림만 그려진 기본 설계도의 개념.
    하나의 메소드로 상속받은 클래스들의 동작을 일관성있게 도와줌.
    모든 멤버변수는 public static final이어야 하며, 이를 생략가능.
    모든 메소드는 public abtract여야하며, 이를 생략가능.
    일반변수는 가질 수 없다.
    여러 인터페이스를 구현할 수 있다.
    인터페이스는 대부분 ~able의 이름을 가짐.
    interface 키워드를 이용해 생성.


버튼 클릭 이벤트

App.class

public class App {
	public static void main(String[] args) {
		SwingUtilities.invokeLater(() -> {	// 프로그램의 안정성을 위해 권장하는 코드. 이 코드블럭안에 코드를 넣어준다.
			new MainFrame("테스트 스윙 앱");	// 새 창 생성
		});
	}
}

MainFrame.class

public class MainFrame extends JFrame{
	public MainFrame(String title) {
		super(title);
		
		setLayout(new BorderLayout()); // 창에 컴포넌트(버튼들)을 붙이기 위함
		
//		JPanel panel = new JPanel();	// JPanel 패널을 생성 => MainPanel으로 옮김
//		panel.setBackground(Color.LIGHT_GRAY);	// 패널 색 지정 => MainPanel으로 옮김
		
		add(new Toolbar(), BorderLayout.NORTH);	// 위쪽에 툴바 붙이기
		add(new MainPanel(), BorderLayout.CENTER);	// 메인프레임에 붙이기 (중앙 가운데 위치)
		
		setSize(600, 400); // 창 사이즈 설정
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 창 닫으면 프로그램 종료
		setVisible(true);  // 창 보이게 설정변경
	}
}

MainPanel.class

public class MainPanel extends JPanel {
	private static final long serialVersionUID = 1L;
	
	public MainPanel() {
		setBackground(Color.lightGray); // 패널 색 설정
	}
}

Toolbar.class

public class Toolbar extends JToolBar{
	public Toolbar() {
		final JButton redButton = new JButton("RED");
		final JButton blueButton = new JButton("BLUE");
		 
		redButton.addActionListener(new RedButtonListener());
		blueButton.addActionListener(new BlueButtonListener());
		
		add(redButton);
		add(blueButton);
	}
	
}
class RedButtonListener implements ActionListener {
	public void actionPerformed(ActionEvent e) {
		System.out.println("Red 버튼 클릭!");
	}
}
class BlueButtonListener implements ActionListener {
	public void actionPerformed(ActionEvent e) {
		System.out.println("Blue 버튼 클릭!");
	}
}


버튼 클릭 시 배경색 바꾸기

application/App.class

public class App {

	public static void main(String[] args) {
		// 자바 swing 프로그램
		// JFrame 은 윈도우창
		SwingUtilities.invokeLater(() -> {	// 스윙의 안정성을 위해 넣는 문장
			new MainFrame("테스트 스윙 앱");
			
		});
	}
}

gui/MainFrame.class

public class MainFrame extends JFrame {
	
	private static final long serialVersionUID = 1L;	// 오류를 없애기 위해 넣는문장

	public MainFrame(String title) {	// 생성자
		super(title);	// 창에 이름을 넣어서 생성
		// 패널 넣기
		// 먼저 레이아웃 설정
		final MainPanel mainPanel = new MainPanel();
		setLayout(new BorderLayout());	// 창에 컴포넌트를 붙이기 위한 레이아웃
		
		add(new Toolbar(mainPanel), BorderLayout.NORTH);	// 위쪽에 툴바 붙이기
		add(mainPanel, BorderLayout.CENTER);	// 위에서 만든 패널을 레이아웃의 가운데 붙이기
		
		setSize(600, 400);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);
	}
}

gui/MainPanel.class

public class MainPanel extends JPanel {
	private static final long serialVersionUID = 1L;
	// 메인패널 클래스는 jPanel을 상속했고 생성시 darkGray색으로 설정
	public MainPanel() {
		setBackground(Color.darkGray);
	}
}

gui/Toolbar.class

class ColorButtonListener implements ActionListener {
	private MainPanel mainPanel;
	private Color c;
	
	public ColorButtonListener(MainPanel mainPanel, Color c) {
		this.mainPanel = mainPanel;
		this.c = c;
	}
	
	public void actionPerformed(ActionEvent e) {
		mainPanel.setBackground(c);
	}
}

public class Toolbar extends JToolBar {
	private static final long serialVersionUID = 1L;
	
	public Toolbar(MainPanel mainPanel) {
		
		final JButton redButton = new JButton("RED");
		final JButton blueButton = new JButton("BLUE");
		// 버튼에 이벤트를 연결('클릭' 이벤트)
		redButton.addActionListener(new ColorButtonListener(mainPanel, Color.red));
		blueButton.addActionListener(new ColorButtonListener(mainPanel, Color.blue));
		
		add(redButton);
		add(blueButton);
	}
}




레퍼런스 변수 (참조형 변수)

  • 객체의 주소값을 가진 변수.
  • 특징 1. 참조형은 변수 자체가 값을 포함하지 않으며, 클래스 인스턴스에 대한 참조(주소)값만 가지고 있다.
    특징 2. 참조형 변수의 선언은 단순히 객체의 위치를 나타내는 메모리만 확보된 상태이므로 객체를 생성하여 그 위치를 참조형 변수에 할당해야 함.

예) Car car = new Car("소나타", 1000000); => 여기서 car가 레퍼런스 변수.

class Person {
	
}

public class App {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Person p1 = new Person();	// p1은 객체가 생성된 위치, 측 주소(address)를 가진다
		System.out.println(p1);		// toString 오버라이딩을 안했을 경우 객체의 주소를 나타냄
		
		Person p2 = p1;		// 주소를 넘김
		
		System.out.println(p2);
		test(p2);	// 메소드 test에 매개변수로 p2전달
	}
	static void test(Person p) {
		System.out.println(p);	// 매개변수로 입력된 p의 주소를 출력한다
	}
}

세 개의 동일한 주소값이 출력된다.

~ 기본형 변수와 레퍼런스 변수의 차이 ~

기본형 변수는 int, double, boolean, char등의 고정크기값을 직접 저장할 수 있음.
단, 객체는 직접 저장 할 수 없는데(크기가 너무 크기때문) 이를 위해 나온 개념이 레퍼런스 변수.
레퍼런스 변수는 객체의 값이 아닌 위치를 저장함.

~ 인스턴스(instance) 란? ~

클래스를 사용하기 위해서는 반드시 메모리에 생성을 해 줘야하는데, 이때 메모리에 생성된 클래스를 클래스 객체 혹은 인스턴스라고 함.
모든 인스턴스는 레퍼런스 변수만을 통해 사용이 가능함.
자바에서 인스턴스 생성 시 new 연산자 필요. ( 예 - new 클래스명(); )

예) Car car = new Car("소나타", 1000000); => 여기서 연산자 new 를 통해 Car(값1, 값2) 인스턴스 생성.


해시코드와 this

해시코드 (hashCode)

  • Object클래스에 정의되어있음.
  • hashcode() 메소드는 객체의 주소값을 이용하여 해시코드를 만들어 반환하기 때문에 서로 다른 두 객체는 절대 같은 해시코드를 가질 수 없다.

this

  • 객체 자신에 대한 레퍼런스.
  • 주로 메소드에서 사용되며 현재 객체를 가리킨다.

~ this와 this() ~
this()는 클래스 내에서 생성자가 다른 생성자를 호출할 때 사용하는 자바코드로, this와는 다름.

toString

예제 1

package hashcode_this;

public class Fox {
	public String name;
	
	public Fox() {
		System.out.println(this);
		this.name = "폭스";
        // ㄴ여기서 this.name은 public String name; 의 name임.
	}
}
package hashcode_this;

public class App {
	public static void main(String[] args) {
		Fox fox1 = new Fox();
		System.out.println(fox1);
		System.out.println(fox1.toString());	// 세 개의 주소결과가 똑같음
		System.out.println(fox1.hashCode());	// 해시코드 출력
		System.out.printf("%x \n", fox1.hashCode());	// 해시코드를 16진수로 출력
		
		System.out.println();
		
		Fox fox2 = fox1;	// fox2에 fox1의 주소를 넘김
		System.out.println(fox2);
		System.out.println(fox2.name); 	// Fox의 메소드를 public으로 지정해줬으므로 바로접근 가능
		System.out.println(fox2.hashCode());	// 해시코드 출력
		System.out.printf("%x \n", fox2.hashCode());	// 해시코드를 16진수로 출력
		
		System.out.println();
		
		System.out.println(System.identityHashCode(fox1));
		System.out.println(System.identityHashCode(fox2));
	}
}


예제 2

// 해시코드
String str1 = new String("ABC");
String str2 = new String("ABC");

System.out.println(str1.equals(str2));
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
// String클래스는 문자열의 내용이 같으면 동일한 해시코드를 반환하도록 
// hashCode()메서드가 오버라이딩 되어있기때문에, 내용이 같은 str1과 str2의 hashCode()를 호출시 같은 값이 나온다.

// 하지만 System.identityHashCode(Object x)는 
// Object클래스의 hashCode메서드처럼 객체의 주소값으로 해시코드를 생성하므로 모든 객체에 항상 다른 해시코드값을 반환한다.
// 즉, str1과 str2는 해시코드는 같지만 다른 객체임.


메소드 로컬 이너 클래스

내부 클래스(inner class 이너 클래스)

  • 클래스 또는 메서드 안에 선언된 클래스.
  • 특징 1. 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근가능.(private멤버 접근가능)
    특징 2. 코드의 복잡성을 줄일수 있음(캡슐화와 관계됨)
  • 외부클래스 A의 내부에 내부클래스 B를 작성할 경우 B클래스는 A클래스를 제외한 다른 클래스에서는 사용빈도나 낮은 것이어야 함.
  • 변수와 동일한 방식으로 선언되며, 변수가 선언위치에 따라 인스턴스 변수, 클래스 변수(static변수), 지역변수로 나뉘듯 내부클래스 역시 선언된 위치에 따라 나뉨.
public class App implements Runnable {
	
	private String name = "미키 마우스";
	
	public static void main(String[] args) {
		new App().start();
	}

	private void start() {
		// activate 메소드를 사용하여 이너 클래스들로 실행하는 방법 - 3가지
		// 1. App 에 인터페이스 Runnable을 구형하여 실행
		activate(this);
		// 2. 익명 클래스 를 이용해 바로 만들기
		activate(new Runnable() {
			public void run() {
				System.out.println(name);
			}
		});
		// 3. 메소드 이너 클래스를 이용
		class Runner1 implements Runnable {
			public void run() {
				System.out.println(name);
			}
		}
		activate(new Runner1());
		
	}
	public void activate(Runnable runnable) {
		runnable.run();
	}

	@Override
	public void run() {
		System.out.println(name);
		
	}
}

아래는 위치에 따른 변수의 종류와 내부 클래스의 종류.
1. 클래스 영역에 선언된 인스턴스 내부 클래스
2. static 내부 클래스
3. (지역변수처럼) 메서드 안에 선언된 지역 내부 클래스(local inner class)

지역 내부 클래스 (local inner class 로컬 이너 클래스)

  • 메서드 안에서 정의된 클래스. 메서드 내에서만 사용가능.
  • 외부 클래스의 모든 멤버에 접근가능.
  • 지역변수는 접근불가, 단 final로 선언된 상수는 접근이 가능.
public class App {
	private String name;	// 객체를 생성해야 사용할 수 있는 인스턴스변수
	
	public App() {
		name = "엘리자베스";
	}
	
	public static void main(String[] args) {
		// 메소드 로컬 이너 클래스
//		System.out.println(name);	// main은 static 메소드이므로 인스턴스변수 사용불가
		App app = new App();
//		app.name = "펭수";
		
		app.run();
	}
	private void run () {
		// 이너클래스 : 클래스 안의 클래스
		// 현재의 경우엔 클래스 안의 메소드에 클래스
//		System.out.println(name); 	// name을 출력하는 메소드
		class Printer {		// 로컬 이너 클래스
			public void print() {
				System.out.println(name);	// 내부 클래스는 상위 클래스의 변수를 사용가능
				// ㄴ 이 name은 App클래스의 name
			}
		}
		// 일반적인 실행
		Printer p = new Printer();
		p.print();
		// 위와 동일. 단 확실한 주소값은 알 수 없다
		new Printer().print();	// print메소드 실행
	} 
}

static 내부 클래스

  • 멤버변수에 static이 있으면 내부 클래스도 스태틱 클래스로 선언해야 함.(단, final static - 상수 인 경우는 해당없음.)
  • static 내부 클래스를 외부에서 생성하려면 outer클래스 객체의 생성없이 바로 생성할 수 있다.
  • static 내부 클래스의 static 변수는 외부 클래스의 인스턴스 멤버에 접근할 수 없다.

익명 내부 클래스

  • 추상 클래스나 인터페이스를 상속받아 단 한 번만 사용할 클래스일 때 익명의 내부 클래스를 만들어 사용.
  • 자세한건 후술.

static과 내부 클래스 비교

  • static 내부 클래스는 외부 클래스의 일반변수에 접근할 수 없고
    non-static 이너 클래스(일반 내부클래스)는 일반변수에 접근가능.
  • 외부에서 내부 클래스를 불러올 때
    내부 클래스 : 외부 클래스의 객체가 필요. 객체를 생성할때도 내부클래스임을 알려야함. (외부클래스객체명.new 내부클래스명)
    static 내부 클래스 : 외부클래스의 이름으로 객체 생성. (new 외부클래스명.내부클래스명)

Person.class

public class Person {
	class Head {
		public void print() {
			System.out.println("헤드");
		}
	}
	static class Body {
		public void print() {
			System.out.println("바디");
		}
	}
	public void print() {
		Head head = new Head();
		Body body = new Body();
		// 내부 클래스는 클래스 안에서 객체를 만들어 사용하면 쉽게 사용할 수 있다
		head.print();
		body.print();
	}
}

App.class

public class App {
	public static void main(String[] args) {
		Person p1 = new Person();
		p1.print();	// 내부에서 사용하면 쉽게 사용가능. 
        	// print메서드 내에서 객체를 사용해 외부클래스에서는 따로 객체생성없이 메서드호출로 간단하게 사용.
		
		// 외부로 내부 클래스를 불러올 때
		// 1. 내부 클래스 (외부 클래스의 객체가 필요함)
		Person.Head head = p1.new Head();	// Person내부의 Head클래스를 호출, 생성할때도 내부임을 짚어줘야함
		head.print();
		
		// 2. 스테틱 내부 클래스 (외부 클래스 이름.내부 클래스 이름 즉, 외부클래스의 이름으로 객체를 생성)
		Person.Body body = new Person.Body();	// 클래스 Person의 Body를 순서대로
		body.print();  
	}
}


익명 클래스

  • 다른 내부클래스와는 달리 이름이 없음.
  • 클래스의 선언과 생성을 동시에 하며, 오직 하나의 객체만을 생성할 수 있는 일회용 클래스.
  • 이름이 없으므로 생성자를 가질 수 없음
  • 오직 하나의 클래스를 상속하거나 하나의 인터페이스만 구현가능.(상속+구현, 다중구현 불가)
  • 형태
    new 부모클래스명() {
    // 멤버선언
    }
    혹은
    new 구현인터페이스명() {
    // 멤버선언
    }
    => 이름이 없으므로 부모클래스(혹은 구현할 인터페이스의 이름)의 이름으로 선언한 후 원래 클래스 내부에 들어갈 내용을 코드블럭{} 안에 작성하면 됨.
public class App {
	private String name = "펭수";
	
	public static void main(String[] args) {
		// 익명 클래스는 이름없는 클래스임
		new App().start();
	}
	
	private void start() {
//		System.out.println(name);
		// Runnable 인터페이스는 객체를 만들 수 없지만 익명 클래스를 바로 만들어 추상 메소드를 구현하여 사용가능 
//		Runnable runner = new Runnable() {
//			public void run() {		// 내부 클래스로써 만든 익명 클래스
//				System.out.println(name);
//			}	
//		};
		Runnable runner2 = new Runnable() {		// 익명클래스로 만든 Runnable클래스
			public void run() {
				System.out.println("헬로! " + name);
			}
		};
//		runner.run();
//		runner2.run();
//		activate(runner);
//		activate(runner2);
		
		// 매개변수로 인터페이스의 객체가 입력될 경우 따로 클래스를 만들지 않고 익명 클래스를 이용해 바로 작성가능
		activate(new Runnable() {
			public void run() {
				System.out.println(name);
			}
		});
		
	}
	public void activate(Runnable runnable) {
		runnable.run(); 	// 인터페이스의 추상메소드 run 실행
	}
}


연습문제 - 계란삶기 타이머

  1. 5초 간격 타이머 "계란 삶는 중"
  2. 30초 간격 타이머 "계란 삶기 완료"
class Task1 extends TimerTask {
	private int i = 0;
	@Override
	public void run() {
		i += 1;
		System.out.println("계란 삶는 중..." + i);
	}
}
class Task2 implements Runnable {

	@Override
	public void run() {
		JOptionPane.showMessageDialog(null, "계란 삶기 완료");	
	}	
}

public class App {
	public static void main(String[] args) {
		// 1. 5초 간격 타이머 "계란 삶는 중"
		// 2. 30초 간격 타이머 "계란 삶기 완료"
		
		// 타이머 1
		Timer timer = new Timer();
		timer.scheduleAtFixedRate(new Task1(), 5000L, 5000L);	// 5초 뒤에 시작, 5초에 한번 반복
		// 타이머 2
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
		executor.scheduleWithFixedDelay(new Task2(), 30, 30, TimeUnit.SECONDS);
		// ㄴ newTask2()를 30초 후에 시작 30초 기준, 초기준으로 실행
	}
}

profile
천 리 길도 가나다라부터

0개의 댓글

관련 채용 정보