복습
변수
1. 지역변수
2. 멤버변수
3. 정적 멤버변수 (= 정적필드, 나중에 공부)
객체모델링
속성 -> 필드
동작 -> 메소드
- 클래스(설계도) -> 객체
- 대부분의 클래스는 객체 생성시 new연산자 사용
필드
- 메소드 바깥쪽에 정의되는 변수
- 힙메모리에 저장되고 자동초기화된다.
- 할당시점 : new연산자로 객체 생성시 힙메모리상에 할당
생성자
- 클래스명과 동일, 앞에는 접근제어자만옴, 필드값을 초기화 시키는 역할
메소드
- 객체의 동적인 것 표현
기본자료형인 필드
- 객체생성시 힙메모리할당되며
- 자동으로 초기화
정수형 : 0
실수형 : 0.0
논리형 : false
참조형인 필드
- null , 참조하는 주소가 없다.
생성자 호출
- 생성자 매개변수에 맞는값 넣어서 생성
생성자 오버로딩
- 한개 클래스 안에 동일한 이름으로 만든 여러 생성자
this
this. : 필드 생성자 메서드 안에서 멤버변수와 매개변수 이름이 같을때사용
this() : 같은 클래스내의 생성자 호출하라는 의미
과제풀이(Lotto.java) 설명
https://laker99.tistory.com/17
- 새로 발생된 난수값과 이전의 난수값들을 비교하기 위해 중첩 for문 사용
- i-- 한 뒤 괜히 더 돌아가는걸 막기위해 break 해주는게 좋다
- 하나 중복을 발견하면 다음꺼부터는 중복일리가 없다, 이전에 이미 다 처리되었기 때문에
- 오름차순 적용
1. num[0] = 5, num[1] = 5 일때, 안의 for문이 돌아가면서 num[0] 과 num[1]을 비교해서 같으므로 i--시킴
2. 그럼 i=0이 되고 break 되어 나옴 이해 바깥 for문에 의해 ++ 되며 i=1 이 됨
3. 다시 num[1]값을 랜덤으로 받는것
오름차순하기
- 정렬알고리즘 통해서 (버블정렬 = 자신의 옆자리에 있는 것과 비교)
- Arrays 클래스의 sort()함수이용해서 num 배열이름 넘겨주면 오름차순 정렬해줌(java.util.Arrays)
필드 = 멤버변수
- 메소드 바깥쪽에 정의됨
- 기본자료형은 초기화가 됨
파일 생성
- 메인메소드를 가진 클래스만 public class가 될 수 있다!
+ 생성자의 접근제어자 주로 public 이다.
클래스 실행하기
1. 클래스 안에 메인메소드 넣기(독립적실행가능)
2. 메인 메소드가 있는 클래스에서 실행시켜주기
필드로 직접 접근하기 예제
class Korean{
//필드
String nation = "대한민국";
String name;
String ssn; //전화번호
public Korean(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
}
public class KoreanEx {
public static void main(String[] args) {
Korean k1 = new Korean("박자바","011225-1234567");
System.out.println("k1.name : " + k1.name);
System.out.println("k1.ssn : " + k1.ssn);
Korean k2 = new Korean("김자바","930525-0654321");
System.out.println("k2.name : " + k2.name);
System.out.println("k2.ssn : " + k2.ssn);
}
}
- k1객체 만든 후 k1. 통해서 필드로 접근
- k2객체 만든 후 k2. 통해서 필드로 접근
- 필드 name, ssn을 private으로 바꾸게 되면 KoreanEx클래스에서 k1.name k2.ssn 해서 접근 불가(출력 불가)
다른 클래스라서, 다이렉트 접근을 막는다
필드값
- nation은 "대한민국"으로 초기화
- name과 ssn은 객체를 생성할때 생성자를 통해 전달된 값으로 각각 초기화됨
할당 시점
- new연산자로 힙메모리에 새로운 공간 생성할때 힙메모리상에 새로운 공간 할당받음(참조형은 다 마찬가지)
- 자료형이 String이라서(필드값) null로 자동초기화되지만 여기선 객체생성시 생성자호출시 받는 값으로 초기화 (name,ssn)
- 주소를가진 레퍼런스 변수 : k1, k2
- 필드들 nation, name, ssn 의 접근제어자가 default이므로 같은 패키지 내는 모두 접근 가능. 그래서 다른 클래스에서 k1.ssn 가능
그림
자바의 접근 제어자 정리
접근제어자 자신의클래스 같은패키지 하위클래스 다른패키지
private O X X X
생략(default) O O X X
protected O O O X
public O O O O
+ 같은 클래스 내 : 접근 제어자 의미 없다. 뭐든 접근 된다.
- private : 오직 같은 클래스에서만 됨 , 다른 클래스에서 접근하는걸 허용하지 않음, 가장 좁은 접근허용
- public : 가장 넓은 접근 허용함, 다른 패키지(폴더) 안의 클래스까지 접근가능
- default : 중간정도 접근 범위, 같은 패키지(폴더) 내에선 다 가능
- protected : 상속관련, 상속 배우고 난후 공부
접근제어자 범위 비교
가장넓음 public > default > protected > private 가장좁음
생성자 오버로딩
- 생성자 여러개 생성
- 구분되는 점이 있어야함
생성자 오버로딩 구분
1. 매개변수 자료형
2. 매개변수 개수
3. 매개변수 순서
메소드
- 언어에 따라 함수라고도 한다
- 쓰는 이유 : 객체의 동작 + 여러개 코드를 모아두고 한번 메소드가 정의되면 호출을 통해 재사용하기 위해서
메소드 만드는법
ex) public static void main(){}
1. 접근제어자
2. static (특별한 경우에만 쓴다,공유 원할때)
3. 자료형 / void
4. 메소드이름 + ()
+ 자바에는 정적 클래스는 없다.
+ 정적 메소드는 흔하지 않다.
접근제어자 예제
package p2022_06_29;
class Calculator {
// 메소드
void powerOn() {
System.out.println("전원을 켭니다.");
return;
}
int plus(int x, int y) { // 지역변수 : x, y, result
int result = x + y;
return result;
// System.out.println("test");
}
//return문 : plus() 메소드를 호출한 곳에 값을 돌려주는 역할
//return문은 메소드 가장 마지막 줄에 사용해야한다.
double divide(int x, int y) {
double result = x / (double) y;
return result;
}
void powerOff() {
System.out.println("전원을 끕니다.");
return;
}
}
public class CalculatorEx {
//p217 ~ 218
public static void main(String[] args) {
Calculator cal = new Calculator();
cal.powerOn();
int result1 = cal.plus(5, 6);
byte x = 10;
byte y = 4;
double result2 = cal.divide(x,y);
System.out.println("result2 : " + result2);
cal.powerOff();
}
}
- Calculator 클래스 앞에 private 쓰면 CalculatorEx 클래스에서 객체를 만들 수 없음(클래스 접근 불가)
- Calculator 클래스의 메소드 powerOff 앞에 private쓰면 cal.powerOn() 을 할 수 없음(메소드 접근 불가)
패키지 = 폴더
- 같은 폴더 안 = 같은 패키지 안
- 서로 다른 폴더 안 = 서로 다른 패키지 안 = public만 접근가능
위 예제의 부분1
int plus(int x, int y) { // 지역변수 : x, y, result
int result = x + y;
return result;
//System.out.println("test"); 오류남
}
+ return문 : plus() 메소드를 호출한 곳에 값을 돌려주는 역할
+ return문은 메소드 가장 마지막 줄에 사용해야한다.
- 메소드가 실행되는동안 필요한 값들을 매개변수로 전달해줌(매개변수 = 지역변수)
- 메소드 실행되는동안만 스택에 값 존재, return되면 자동으로 지워짐
- 그래서 메모리 관리 크게 신경안써도 된다.
위 예제의 부분2
cal.powerOn();
- 메인 메소드 안에서 객체 생성하며 생성자 호출후 메소드 호출
- 필드처럼 메소드에 접근할때도 객체.메소드() 로 호출함 (정적메소드가 아닌경우)
위 예제의 부분3
int result1 = cal.plus(5, 6);
- Call by Value로 값 전달하며 메소드 호출
- plus()는 결과를 리턴자료형 int로 돌려줌 그래서 int result1으로 받아야하는것
위 예제의 부분4 (주의)
double divide(int x, int y) {
double result = x / (double) y;
return result;
}
- 리턴자료형이 double이므로 double형 값을 돌려준다
- int형인 x와 int형인 y를 산술연산시 int형으로 처리됨
ex) x = 10 , y = 4 이면 다 잘리고 2만 나온다. (손실 발생)
- 손실 발생을 막기 위해서 (double)을 붙여줘서 강제 형변환 -> 2.5
- 그걸 double로 return 시켜줌
- (double) 을 빠뜨리면 자동형변환되어서 2 -> 2.0 이 나옴(손실발생)
- 10/4 는 2.5이므로 살리려면 (double)해서 강제형변환시켜서 값이 double로 처리되도록 해야함.
위 예제의 부분5(CalculatorEx의 main 메소드 내부)
byte x = 10;
byte y = 4;
double result2 = cal.divide(x,y);
System.out.println("result2 : " + result2);
+ byte는 1바이트 정수형 자료형
- cal.divide에서 매개변수로 byte를 넘김 -> 근데 devide 메소드의 매개변수는 int 형 4바이트 이다.
- 4바이트짜리 변수에 1바이트 짜리 값을 할당 => 컴파일러가 자동형변환시켜줌. 큰자료형으로 작은값으로 들어가므로
- 자동형변환되어서 위 코드 가능함
+ plus와 divide 메소드의 매개변수 이름들이 같지만 그 메소드 내에서만 사용되는 지역변수이므로 충돌이 발생하지 않음
메소드의 매개변수에 참조형이 오는 예제
- 값이 아닌 주솟값을 전달해야한다.
- sum1은 배열에 전달된 값들의 합을 돌려주는 함수
package p2022_06_29;
//p220 ~ 221
class Computer {
// 주솟값 전달에 의한 메소드 호출(Call by Reference 방식)
int sum1(int[] values) {
int sum = 0;
for (int i = 0; i < values.length; i++) {
sum += values[i];
}
return sum;
}
// vargus : 전달된 값은 배열로 받음
int sum2(int... values) {
int sum = 0;
for (int i = 0; i < values.length; i++) {
sum += values[i];
}
return sum;
}
}
public class ComputerEx {
public static void main(String[] args) {
Computer com = new Computer();
int[] values1 = { 1, 2, 3 };
int result1 = com.sum1(values1); // sum1() 메소드 호출 , 주솟값을 가진 values1을 넘김
System.out.println("result1 : " + result1);
int result2 = com.sum1(new int[] { 1, 2, 3, 4, 5 }); // sum1() 메소드 호출 , 배열을 만들어서 그 주소를 넘김
System.out.println("result2 : " + result2);
int result3 = com.sum2(1, 2, 3); // sum2() 메소드 호출 ,
System.out.println("result3 : " + result3);
int result4 = com.sum2(1, 2, 3, 4, 5); // sum2() 메소드 호출 ,
System.out.println("result4 : " + result4);
}
}
- "같은 int형 배열"의 주소값을 써서 메소드를 호출해야함
- Computer 클래스의 참조형변수인 int[] values는 아직 주솟값이 없는 상태 , new를 써서 int형 배열을 만들어 주솟값을 가진 걸 넘겨줘야함
- 그래서 main쪽에 int형 1차원배열을 만들어줌
- sum1에서 참조형이 매개변수로 왔으므로 같은 int형 배열의 주솟값을 넘겨야함
- new연산자를 아직 안썼으므로 아직 int[] values는 가리키는게 없음
- 주솟값 전달에 의한 메소드 호출임
+ 값전달보다 주솟값 전달이 더 효율적이고 빠른 방식임
vargus (...)
- ...은 vargus 라고 함
- 전달된 값들은 1개일수도 여러개일수도 있지만 전달되는 값들은 모두 배열로 받음
- 매개변수가 있으면 매개변수의 개수대로 메소드 호출시 써줘야하지만 ... 은 int값을 1개 전달하든 여러개 전달하든 모두 배열로 처리됨
Call by Reference
- values1 int형 배열 힙메모리상에 4바이트 공간 3개 만들어지고 1,2,3이 들어가있고, 그 걸 values1이 그 스택에서 그 배열의 주소를 가지고 있음
int result1 = com.sum1(values1)
- 에서 주솟값을 담고있는 values1을 전달 = 주소를 전달 = Call by Reference
- 주솟값이 없었던 Computer 클래스의 sum1의 values는 주솟값을 전달받아서 Computer 클래스의 values는 이제 이 {1,2,3} 배열을 가리키게됨
- 합을 구해서 리턴형 int로 result1에 값을 돌려줌
- 매개변수 중에서 참조형이 오는 경우 중 클래스/인터페이스가 가장 많다(배열은 많진 않다)
- API 제공 클래스 + 사용자 정의 클래스 => 이것들도 자료형이다
- 참조형변수는 주솟값을 가진다
매개변수에 주소값을 전달하는 법(위 예제 부분)
int[] values1 = { 1, 2, 3 };
int result1 = com.sum1(values1); // sum1() 메소드 호출 , 주솟값을 가진 values1을 넘김
System.out.println("result1 : " + result1);
int result2 = com.sum1(new int[] { 1, 2, 3, 4, 5 }); // sum1() 메소드 호출 , 배열을 만들어서 그 주소를 넘김
System.out.println("result2 : " + result2);
1.이렇게 values1 처럼 만들어진 배열공간의 주소값을 전달하는방법이 있다.
- values1이 가진 주솟값을 전달
2.(new int[] { 1, 2, 3, 4, 5 } 처럼 오른쪽의 new연산을 써서 새로운 공간을 만들어서 그것의 주솟값을 넘겨줄 수도 있음
- 기억공간이 만들어지고 그거의 주솟값이 sum1 메소드의 매개변수로 넘어감
- 값을 받는 변수가 없기 때문에 이건 재사용이 안된다. 이후 가비지 콜렉터가 이 힙메모리상 배열을 지워버린다.
- 더이상 이 힙메모리에 접근할수없을때 해제된다.
- 할당시점은 new를 썼을때 할당된다
그림
+ 배열 생성
int[] score = {80,90,100} // new안쓰지만 힙메모리에 데이터 개수만큼 공간 만들어짐
int[] score = new int[]{80,90,100}
+ 배열등의 참조형을 매개변수로 쓰는이유
- 매개변수가 100개라면?
- 값들은 힙메모리에 저장시켜놓고 주솟값만 매개변수로 전달하는게 훨씬 효율적
vargus(위 예제 부분)
// vargus : 전달된 값은 배열로 받음
int sum2(int... values) {
int sum = 0;
for (int i = 0; i < values.length; i++) {
sum += values[i];
}
return sum;
}
- 메소드의 매개변수가 2개면 2개 전달 1개면 1개 전달 해야했었다.
- but vargus쓰면 1개 전달하든 2개전달하든 3개전달하든 모두 매개변수로 받을 수 있다
- 전달된 값은 모두 배열로 처리됨
ex) 첫번째 전달된값은 0번방 두번째 전달된 값은 1번방...
- 다만 자료형은 맞춰줘야함 int ... values 면 몇개이든 int형 값을 전달해야함
int result3 = com.sum2(1, 2, 3); // sum2() 메소드 호출 ,
System.out.println("result3 : " + result3);
- 1,2,3을 sum2로 전달하면 values가 1,2,3 을 배열로 저장함
- 전달된값들은 "같은 int형 배열"로 처리됨
- 자주쓰는 기능은 아님
int result4 = com.sum2(1, 2, 3, 4, 5); // sum2() 메소드 호출 ,
System.out.println("result4 : " + result4);
- 로 하면 1,2,3,4,5를 넘기고 배열로 받아서 처리해줌
메소드안에서 다른 메소드를 호출하기
- 여태껏은 메인메소드안에서만 메소드를 호출했었다
- 이젠 main 메소드가 아닌 일반 메소드안에서 다른 메소드를 호출해보자
package p2022_06_29;
// p227
// 클래스 내부에서 메소드 호출 : 메소드 안에서 다른 메소드 호출
class Calculator1 {
int plus(int x, int y) {
int result = x + y; //17
return result;
}
double avg(int x, int y) {
double sum = plus(x, y); // plus() 메소드 호출
double result = sum / 2;
return result;
}
void execute() {
double result = avg(7, 10); //avg() 메소드 호출
println("실행결과 : " + result); //println() 메세지 호출
}
void println(String message) {
System.out.println(message);
return;
}
}
public class CalculatorEx1 {
public static void main(String[] args) {
Calculator1 mycal = new Calculator1();
mycal.execute(); // execute() 메소드 호출
// execute(); //오류발생
}
}
1. 메인메소드에서 메소드 호출시
- 객체.메소드() 해서 메소드를 호출해야함
ex)
Calculator1 mycal = new Calculator1();
mycal.execute(); // execute() 메소드 호출
// execute(); //오류발생
2. 일반메소드에서 메소드 호출시(같은 클래스 내부)
- 객체.메소드() 가 아니라 객체없이 그냥 메소드() 만으로 호출 가능하다!
ex)
double result = avg(7, 10);
- avg() 메소드 호출 그냥 avg(7,10)만으로 호출했음
위 예제 흐름
- JVM이 main메소드 실행 -> mycal.execute() 호출함-> execute()이 실행됨
-> 그 메소드에서 avg메소드를 호출하면서 매개변수를 7, 10 넘겨줌
-> avg 매개변수의 x에 7, y에 10이 들어감, avg에서 다시 plus() 메소드를 7,10을 넘겨주며 호출
-> plus() 메소드에서 합을 구해서 합을 result 해서 plus()를 "호출"한곳에 값을 돌려줌 즉 avg로 돌아감
-> 그걸 avg의 sum이 받아서 %2 한뒤 돌려줌
- (주의) plus()가 돌려줄땐 int형인 17을 돌려줌, 근데 sum을 double로 받았으므로 17.0 해서 /2 해서 8.5가 result가 됨
-> 그 8.5 값을 avg를 호출한 execute()에 돌려줌, 그걸 println()메소드에 결과값을 넘겨서
-> println()이 출력시킴
+ 자바는 객체지향언어라서 호출할 메소드가 뒤에있어도 상관없다
+ execute() 아래 println() 있지만 execute()에서 println() 호출가능
+ 일반 메소드끼리는 메소드 이름만으로 서로를 호출한다
메소드 오버로딩(Method Overloading)
- 1개의 클래스 안에 동일한 이름을 가진 메소드를 여러개 정의하는 것.
- 생성자 오버로딩 처럼 메소드도 한개의 클래스 안에 동일한 이름의 클래스 여러개 정의할 수 있다
메소드 오버로딩 구분하는 법
1. 매개변수 자료형
2. 매개변수 개수
3. 매개변수들의 순서
- 한가지를 이상을 만족해서 구분이 되어야만 올바르게 메소드 오버로딩 시킨것임
* 나중에 메소드 오버라이딩과의 차이를 알아야 한다. 완전히 다른 개념
+ 메소드 오버라이딩 : 두 클래스 사이에 상속이 정의되어야만 사용가능
String 클래스 (API 문서)
- 필드 생성자 메소드 순
- 필드 : 메서드 바깥쪽에 정의되는 변수
- 생성자 : 클래스명과 동일, 형식상 괄호가 반드시 있다
- 매개변수가 없는 생성자는 기본생성자
- 나머지는 클래스명과 동일하게 생성자가 만들어지는데 구분이 되도록 정의됨
-> 생성자 오버로딩
- 메소드도 똑같은 이름을 가진 메소드들이 있다
ex) copyValueOf(char[] data) 와 copyValueOf(char[] data, int offset, int count)
- equlas 메소드, format 메소드들은 같은이름인 메소드가 여러개다 -> 메소드 오버로딩
메소드 오버로딩
class Calculator2{
//정사각형의 면적을 구해주는 메소드
double areaRectangle(double width) {
return width * width;
}
//직사각형의 면적을 구해주는 메소드
double areaRectangle(double width, double height) {
return width * height;
}
}
- 메소드가 같은 이름이다
- 매개변수가 1개인 areaRectangle 과 매개변수가 2개인 areaRectangle
- 매개변수의 개수가 다르므로 올바르게 메소드 오버로딩 되었음
- 매개변수 1개 전달하면 매개변수 1개인 areaRectangle() 이 호출되고 2개 전달하면 매개변수 2개인 areaRectanble()이 호출됨
- 메소드 오버로딩은 아무 언어나 되는게 아니라 객체지향언어만됨(c++,java는 되고 c는 안됨)
앞으로 공부할 내용
1. static이 붙은 정적 필드(정적변수)
2. static이 붙은 정적 메소드
변수
지역변수
멤버변수
정적변수
중에서 정적변수를 하는 것이다
3. 상수를 만드는 법 (Constant)
- final을 붙이거나 static final 을 붙인다
- 기존 변수에 final 붙이면 상수가 되는것
- final을 클래스, 필드, 메소드 에 붙일 수 있다-> 어디에 붙이냐에 따라 의미가 달라짐
4.접근제한자와 패키지 의 관계
5. getter와 setter 메소드
정적 필드 (정적 변수)
- static 이 붙은 변수
ex)
static int a; // 그냥 변수앞에 static을 붙이면 정적변수가 됨
- static : 공유를 목적으로 해서 누구나 쉽게 접근하도록 만들때만 static 붙인 정적 필드형태로 만듬
- API 보면 클래스들 정적필드, 메소드들 정적메소드들 엄청 많다
ex) Math 클래스안엔 정적필드랑 정적메소드뿐
- 정적 필드는 클래스명.필드 로 사용
- 누구나 쉽게 생성할수있도록 따로 객체생성없이 클래스명.필드로 사용함
ex)
int n = Integer.parseInt(String s);
- 에서 parseInt는 정적메소드라서 이렇게 클래스명.parseInt()로 사용함
- 세번째 변수인 정적멤버변수(정적 필드)
- 정적 필드 = 정적 멤버변수 = 정적 변수
- 메모리상에 할당되는 시점과 저장되는 영역이 (지역변수,멤버변수, 정적변수) 다 다르다(잠시 후 공부)
Math 클래스(정적 필드/메소드 예시)
- 내부에 정적 필드 뿐이다
- 정적필드 정적메소드 뿐
- 정적필드 E , PI 는 Math.E 로 접근가능
- 정적 메소드 , 정적 필드뿐이라 객체 생성할 필요가 없음
- math클래스는 생성자가 아무것도 없다 -> 생성자 자체를 못씀
- random() 메소드의 타입은 static double이다. 즉 정적메소드이고 리턴값이 double
- 정적 메소드 이므로 Math.random()으로 사용할수있다
정적변수의 메모리 할당 시점/장소
- 할당 시점 : 정적 멤버변수는 클래스가 메모리에 로딩되면(실행될때) 할당됨
- 초기화 : 자동으로 설정되고 (정수형 0 실수형 0.0 논리형 false)
- 값 유지 : 프로그램이 종료될때까지 값을 유지한다
- 저장 영역 : 메모리의 static영역 (공유영역)에 자동으로 할당됨
- 저장 영역 : 공유영역 = 메소드영역 = static 영역 <->힙,스택과 다름
- 주의 : 장 오래 메모리에 상주하므로 많이쓰면 메모리가 부족해짐, 특별한경우아니면 만들지 않음
+ 특별한 경우, 반드시 정적멤버변수 만들어야하는 경우
- 싱글톤 : 객체생성을 1번만 하는 것, 이 경우엔 정적필드를 만들어야함
자동초기화
- 정적멤버변수는 자동초기화 된다.
- 배열과 마찬가지로 int는 0 , double 은 0.0, boolean은 false로 초기화 된다.
- 메소드영역에 값을 저장하고 한번 저장된값은 프로그램이 완전히 종료될때까지 값을 유지
정적 멤버변수에 접근하는 방법
- 정적 변수를 가지고 있는 클래스명으로 직접 접근 할 수 있다.
class StaticTest{
satic int a;
public static void main(String[] args) {
System.out.println(StaticTest.a)
}
}
- 정적메소드 사용시 클래스명.메소드()
정적 필드 / 정적 메소드 정리
- 정적 필드 = static이 붙은 변수
- 정적 메소드 = static이 붙은 메소드
- static이 붙은 필드를 가진 클래스가 로딩이 되면 이 정적필드는 메소드 영역에 저장이되고
자료형에 따라 기본자료형 변수들은 자동초기화됨
정적 멤버변수 만드는 법
- static 붙이기
- 예제 : NoneObject.java/UseNoneObject.java / UseNoneObject.java에만 메인메소드 있음
package p2022_06_29;
public class NoneObject {
static int number = 3; // 정적 필드
public static void printNumber() { // 정적 메소드
System.out.println("number = " + number);
}
}
- 메인메소드없으므로 독립적 실행은 안됨,
- 정적필드하나 정적메소드 하나 있는 NoneObject 클래스
- 이 클래스의 필드에 접근하고 메소드를 호출해보자
package p2022_06_29;
public class UseNoneObject {
public static void main( String[] args ) {
//이 방식 잘 안쓴다
NoneObject no = new NoneObject();
System.out.println("no.number = " + no.number);
no.printNumber();
//아래방식을 주로 쓴다
System.out.println("NoneObject.number = " +
NoneObject.number);
NoneObject.printNumber();
}
}
출력
no.number = 3
number = 3
NoneObject.number = 3
number = 3
- NoneObject 클래스는 정적필드와 정적메소드를 가지고 있다
- UseNoneObject 클래스에서 접근할때 객체를 생성한 후 필드와 메소드를 사용할 수도 있지만
- 정적 필드와 정적메소드이므로 이렇게 안쓴다 (가능은 하지만 안쓴다)
- 필드 접근/메소드 호출하는 법 : NoneObject 클래스에서 .을 찍어서 정적필드, 정적메소드 로 접근
정적 필드 / 정적 메소드 접근 정리
- 누구나 쓸수있도록 하려고 static 붙인거임-> 객체생성해서 접근하지 않는다
- 결론 : 정적필드 , 정적메소드는 클래스명.필드, 클래스명.메소드() 해서 접근해라
정적 필드의 자동초기화
- 예제 : NoneObject.java 파일
package p2022_06_29;
public class NoneObject {
static int number = 3; // 정적 필드
public static void printNumber() { // 정적 메소드
System.out.println("number = " + number);
}
}
- 에서 static int number 에 3 초기화 안시킨다면?
- 자동으로 0으로 초기화시켜주므로 UseNoneObject 에서 실행해보면 0으로 출력됨
정적 멤버변수의 메모리 할당/해제(위 예제)
- NoneObject클래스가 로딩되자마자 number는 공유영역에 저장되면서 int형이니 0으로 초기화됨
- number의 값은 프로그램 종료시까지 계속 메모리에 상주함
- static 정적 필드 많이쓰면 메모리 부족
객체를 생성해서 정적필드/정적메소드에 접근(위 예제 부분)
//이 방식 잘 안쓴다
NoneObject no = new NoneObject();
System.out.println("no.number = " + no.number);
no.printNumber();
- 가능은 하다
Q. 그럼 이때 new를 쓰면 정적필드는 어디로 저장될까? (질문하기) -> 그래도 메소드 영역
메모리 정리
- 힙 메모리에 저장 : JVM 시작할때 생성, 객체와 배열 저장, 가비지 콜랙터가 참조되지 않는 즉, 사용될수 없는 객체 제거
- 메소드영역 메모리 : static이 붙은 필드를 가진 클래스가 로딩이 되면 이 정적필드는 메소드 영역에 저장이됨.
static 필드의 의미(그 안의 값)
정적 필드 값의 유지 예시1 : StaticCount.java, Shared.java / 메인메소드는 Shared 클래스에만 있다
package p2022_06_29;
public class StaticCount {
static int number = 3;
}
- 정적 필드 하나만 있다. 프로그램 종료까지 값을 계속 유지함
- 정적필드는 싱글톤을 만들때만 주로 쓴다. 객체를 1개만 생성해서 공유를 한다는 개념인 싱글톤
package p2022_06_29;
public class Shared {
public static void main( String[] args ) {
StaticCount sc1 = new StaticCount();
System.out.println("sc1.number = " + sc1.number); //3
sc1.number++;
System.out.println("sc1.number = " + sc1.number); //4
StaticCount sc2 = new StaticCount();
System.out.println("sc2.number = " + sc2.number); //4
sc2.number++;
System.out.println("sc2.number = " + sc2.number); //5
StaticCount.number ++;
System.out.println("StaticCount.number = " + StaticCount.number); //6
}
}
출력
sc1.number = 3
sc1.number = 4
sc2.number = 4
sc2.number = 5
StaticCount.number = 6
- 에서 StaticCount 클래스로 sc1라는 객체를 만들었음
- (정적필드, 정적메소드 가진 클래스로 객체 만드는건 자주안하지만 가능하긴 함)
- sc1을 통해서 접근 된다!
값의 변화(값이 유지됨)
1. 처음 sc1.number 출력했더니 3 을 출력함 = 초기값이 3임
2. sc1.number++; 해서 sc1통해서 증가시킴
3. sc1.number 출력시 4를 출력함
- 그 후 같은 StaticCount로 sc2 라는 새로운 객체도 만듬
4. 근데 힙메모리 영역이 아니라 메소드 영역이라는 공유영역이기 떄문에 sc2.number로 sc2에 접근해서 출력해도 number는 3이 아닌 가장 마지막에 저장한 값 4를 출력시킴
5. sc2.number 로 또 접근해서 ++ 증가시킴
6. sc2.number 출력시 5를 출력
- 마지막엔 객체가 아닌 StaticCount.number 로, 즉 클래스 명으로 접근한다
6. StaticCount.number 를 ++
7. 출력하면 6이 나옴
정적 필드 값의 유지 예시2 : StaticTest01.java
package p2022_06_29;
class StaticTest {
static int a = 10; // 정적 필드 : 메소드영역(공유영역) 에 저장
int b = 20; // 인스턴스 멤버변수 : heap 메모리에 저장
}
class StaticTest01 {
public static void main(String[] args) {
System.out.println("StaticTest.a->" + StaticTest.a);
StaticTest s1 = new StaticTest();
StaticTest s2 = new StaticTest();
System.out.println("s1.a->" + s1.a + "\t s2.a->" + s2.a);
System.out.println("s1.b->" + s1.b + "\t s2.b->" + s2.b);
s1.a = 100;
System.out.print("s1.a->" + s1.a);
System.out.println("\t s2.a->" + s2.a + " ");
System.out.println(StaticTest.a);
s1.b = 200;
System.out.print("s1.b->" + s1.b);
System.out.println("\t s2.b->" + s2.b);
}
}
- StaticTest01이 메인메소드가지고 있다
- StaticTest 클래스에는 static 정적 변수 1개, 일반적인 필드 1개 이렇게 있다
- 이런 일반적인 필드 는 instance 변수 또는 instance 필드 라고 부름
- 즉 a는 정적필드 b는 인스턴스필드
정적필드와 인스턴스 필드 비교
- StaicTest.a 는 정적필드기 때문에 클래스명. 으로 접근 가능
- 이때 초기값이 10으로 있기떄문에 10으로 출력됨
- 그 후 StaticTest 클래스로 new해서 객체 2개를 생성함 s1, s2
- new연산자로 할떄마다 힙메모리상에 새로운 공간을 할당받음
- 그래서 인스턴스 멤버변수인 b는 힙 메모리상에 할당받음
- 정적 변수인 a는 그대로 공유 영역에 있음
현재 상태
a=10 //정적 멤버변수
(공유영역)
s1 -------> b=20
s2 -------> b=20
(Stack영역) (Heap영역)
초기 상태
- b는 s1의 b, s2의 b 각각 있고, 힙메모리 상에 20으로 각각 있다.
- 이 값의 주소를 s1, s2가 스택메모리에서 갖고 있음
- a는 정적멤버변수라서 공유영역(메소드영역) 에 초기값 10을 저장하고 있는 상태
- 그래서 s1.a, s2.a는 10으로 출력됨 s1.b나 s2.b는 20으로 출력됨
변화
- s1.a 에 접근해서 값을 100으로 수정함
- s1.b 에 접근해서 값을 200으로 수정함
a=100 //정적멤버변수
(공유영역)
s1 -------> b=200
s2 -------> b=20
(Stack영역) (Heap영역)
변화된 상태
- s1.a 와 s2.a 모두 출력시 100이 나옴
- 또한 클래스명으로 접근한 StaticTest.a 해도 100이 출력됨
- 즉 a는 가장 마지막으로 저장된 값이 공유됨
- s1.b만 200으로 수정됨(공유 안됨)
- s1.b를 출력하면 200, s2.b를 출력하면 20이 나옴
정적필드와 인스턴스 필드의 비교
- 정적 필드들은 마지막에 저장된 값을 계속 공유함
- 인스턴스 필드들은 클래스를 가지고 객체 생성할때마다 힙 공간 할당받으며 각각 초기값을 가지게 됨
정적 필드와 접근제어자 예제 : StaticTest02,03,04
StaticTest02.java
package p2022_06_29;
class StaticTest2 {
private static int a = 10; // 정적 필드
private int b = 20; // 인스턴스 필드
public static void setA(int new_a) { // 정적 메소드
a = new_a;
}
public static int getA() { // 정적 메소드
return a;
}
}
public class StaticTest02 {
public static void main(String[] args) {
// System.out.println(StaticTest2.a); //a가 private으로 선언되어서 컴파일 에러 발생
System.out.println(StaticTest2.getA());
StaticTest2 s1 = new StaticTest2();
StaticTest2 s2 = new StaticTest2();
s1.setA(10000);
int res1 = s1.getA();
System.out.println(res1);
System.out.println(s2.getA());
}
}
출력
10
10000
10000
- StaticTest2.a 로 접근이 안된다.
- 이유 : 아무리 정적필드라도 외부클래스의 접근을 허용하지 않는 private 이 a 앞에 붙어서 다른 클래스인 StaticTest02 에서 접근시 에러 생김
private static int a = 10;
- 모순이 발생한다
- static은 공유를 하겠다는거고 private은 보호를 하는거라서 컴파일상 오류를 발생시키며 접근이 불가능하다.
- 결과적으로는 클래스명으로 접근하는 것이 허용되지 않음
private인 static 필드값 가져오는 방법
- getA() 메소드를 이용해서 접근한다
- getA() 메소드는 정적 메소드이고 public 이라 클래스명으로 접근가능하다\
ex)
System.out.println(StaticTest2.getA());
- 가능하다, 10이 출력됨
private인 static 필드값 설정하는 방법 (위 예제 부분)
StaticTest2 s1 = new StaticTest2();
StaticTest2 s2 = new StaticTest2();
s1.setA(10000);
int res1 = s1.getA();
System.out.println(res1);
System.out.println(s2.getA());
- StaticTest2로 새로운 객체 2개 s1, s2 생성함
- setA 메소드는 public static 이라 호출 가능하다. 클래스명으로 접근 가능하지만 이렇게 s1.setA(10000)처럼 객체로 접근도 되긴함
- s1.set(10000)으로 a값을 10000으로 셋팅
- s1.getA() 해서 접근도 되고, s2.getA() 해서도 됨 둘다 출력시 10000 됨
정적 필드와 접근제어자 결론
- private static하면 모순이 생겨서 결국은 다른 클래스에서 접근 못하는 필드임
- 그래서 StaticTest2.a가 에러생김(다른 클래스에서 호출하였기 떄문에)
-> private이 붙으면 정적 static 필드라도 다른 클래스에서 접근이 불가능함
정적 필드와 접근제어자 주의
- 정적 메소드 안에는 정적 필드만 사용가능함 (중요)
- 정적 메소드 안에 일반적인 필드 쓰면 오류발생 (저장영역과 기능이 다르기 떄문에)
- 정적메소드와 일반메소드는 기능이 다름
정적 필드 / 정적 메소드 주의 예제1 : StaticTest03
package p2022_06_29;
class StaticTest3 {
private static int a = 10; // 정적 필드
private int b = 20;
public static void printA() { // 정적 메서드에서는 this를 사용하지 못함
System.out.println(a);
// System.out.println(b); //정적메소드 안에는 일반적인 필드 사용불가
// System.out.println(this.a); //컴파일 에러 발생
}
public void printB() { // this는 인스턴스 메서드에서 여러 객체에 의해서
System.out.println(b); // 메서드가 호출될 때 이를 구분하기 위해서 사용된다.
}
}
public class StaticTest03 {
public static void main(String[] args) {
StaticTest3.printA();
StaticTest3 s1 = new StaticTest3();
StaticTest3 s2 = new StaticTest3();
s1.printB();
s2.printB();
}
}
정적메소드를 쓸 때 주의사항
1. 정적 메소드에서는 this 레퍼런스 변수를 사용할 수 없다.
- this.는 힙메모리의 주소를 참조하는게 this.이고, this.는 개별적인 객체에 대한 주소를 가진 레퍼런스 변수라서 정적메소드 안에선 this. 를 사용할 수 없다
2. 정적 메소드에서는 일반적인 변수(인스턴스 필드)를 사용할 수 없다.
- 정적 메소드에서는 정적멤버변수만 사용가능함
- 일반적인 필드 = 힙메모리 이고, 정적필드 = 메소드 메모리(영역) 이라서 영역이 다르기 때문
3. 정적 메소드는 메소드 오버라이딩되지 않는다.
- static이 붙은 정적메소드는 메소드 오버라이딩이 되지 않는다.
+ 메소드 오버라이딩 : 두 클래스 사이 상속관계가 있을때, 부모로부터 상속되는 필드와 메소드 중, 메소드를 상속받을때, 이름과 형식은 똑같이 하면서 내부 내용은 다르게 쓰는 것.
정적 필드 / 정적 메소드 주의 예제2 : StaticTest04
package p2022_06_29;
class StaticTest4 {
private static int a = 10; // 정적필드
private int b = 20;
public static void printA() {
System.out.println(a);
// System.out.println(b); //컴파일 에러 발생 : 정적메소드 안 일반필드
}
public void printB() {
System.out.println(b);
}
}
public class StaticTest04 {
public static void main(String[] args) {
StaticTest4.printA();
StaticTest4 s1 = new StaticTest4();
StaticTest4 s2 = new StaticTest4();
s1.printB();
s2.printB();
}
}
- 정적메소드 안에는 정적필드만 사용가능하다
- 일반 메소드 안에서만 일반필드 사용가능
- 정적 메소드 , 정적 필드는 클래스명. 해서 사용함 ! StaticTest4.printA();
- 하지만 객체를 만들어서 객체.메소드 해서도 메소드 호출가능, 객체.필드 해서 필드 접근하는것도 가능
StaticTest4 s1 = new StaticTest4();
StaticTest4 s2 = new StaticTest4();
s1.printB();
s2.printB();
Math 클래스 (API 문서)
- 모두 정적필드 뿐
- 모두 정적메소드 뿐
- Math 클래스를 가지고 정적 필드와 정적메소드를 사용해보자
Math의 정적필드 2개
- E
- PI
Math의 생성자
- 생성자 자체가 없다
- 생성자는 제공되지 않는데 쓸수없다 Math 클래스로 new해서 객체생성하면 오류발생
Math의 메소드
- 모두 static 붙은 정적 메소드 -> 굳이 객체를 생성할 필요도 없다
- 그냥 Math.E , Math.random() 으로 쓰면됨
Math 클래스의 필드 사용 예제
+ Math라는 이름으로 사용자 지정 클래스 생성할 수 없다. 라이브러리에 이미 쓰고 있는 이름이라서
System.out.println("E = " + Math.E);
System.out.println("PI = " + Math.PI);
- Math클래스는 생성자가 제공되지 않기때문에 Math 클래스로 직접 객체를 생성할 수 없다
- 하지만 Math 클래스 = 정적 필드 + 정적 메소드 뿐이라서 객체 없어도 아무 문제 없음
Math 클래스 메소드 사용 예제(다양한 메소드 사용)
1. abs() 메소드
- 절대값을 구해서 결과를 돌려줌
- abs() 메소드 여러개임 (메소드 오버로딩) , int, double, long, float등 타입별로 존재
System.out.println("abs() = " + Math.abs(-10)); //절대값 , 결과는 10
2. ceil() 메소드
- 안에있는 소수점 이하 값을 무조건 올려버림 (올림) , 소수 첫째자리에서 올림함
System.out.println("ceil() = " + Math.ceil(3.14)); //올림, 출력은 4.0
3. round() 메소드
- 반올림을 해주는 메소드
- 소수첫째자리에서 반올림해주고 결과는 long으로 돌려줌 11이 나옴
- 매개변수 double, 반환은 long
System.out.println("round() = " + Math.round(10.5)); //반올림, 출력은 11
4. floor() 메소드
- 무조건 내림을 해주는 메소드
- 소수점 이하는 다 버림, 매개변수 double, 반환은 double
- 10.9 -> 10.0
System.out.println("floor() = " + Math.floor(10.9)); //내림, 출력은 10.0
5. max() 메소드
- 최대값을 구해주는 메소드
- 메소드 오버로딩이 되어있다
ex) max(int a, int b) , max(double a, double b) 등
- 2개짜리 매개변수 있는 메소드들만 있다
- 자바에는 2개짜리만 제공함 -> 3개 이상이 필요하면 내가 만들어서 써야함 (파이썬은 제한 없음)
- 10과 20중 큰 값을 return 구문으로 돌려줌
System.out.println("max() = " + Math.max(10, 20)); // 최대값 , 출력은 20
5. min() 메소드
- 최솟값을 구해주는 메소드
- 메소드 오버로딩이 되어있다
- min메소드도 2개짜리 매개변수 있는 메소드들만 있다
System.out.println("min() = " + Math.min(10, 20)); // 최솟값, 출력 : 10
6. pow() 메소드
- 지수를 구할때 사용한다.
ex) 2의 3승 구할때 쓰는 메소드 Math.pow(2,3);
- pow(double a, double b) 이고 리턴은 double 형
System.out.println("pow() = " + Math.pow(2,3)); // 2의 3승 , 출력은 8.0
7. random() 메소드
- 양수값으로 0.0보다 크거나같고 1.0보다 작은 범위의 실수형태의 난수 리턴
- 0.0 <= Math.random() < 1.0 범위의 난수 발생
- double형으로 리턴
System.out.println("random() = " + Math.random());
8. sqrt() 메소드
- 제곱근을 구해주는 메소드
- 루트를 씌운값을 돌려줌
- 리턴은 double
ex) 루트5를 구할때 쓰는 메소드 Math.sqrt(5);
System.out.println("sqrt() = " + Math.sqrt(5)); //루트5(제곱근5) , 출력은 2.23606...
우리가 쓰는 println() 의 구조
System.out.println("");
- System 클래스의 필드를보면 모두 정적필드
- 객체생성안하고 System.out으로 썼었던 이유 : 정적필드 중 out 이 있다
- out이 정적필드이기때문에 System.out을 쓴다
- 이 out이란 필드의 결과를 돌려주는 자료형이 PrintStream 이 리턴형이다 (객체로서 리턴)
- 즉 System.out은 자료형이 PrintStream이라는 자료형
- 이 PrintSteram 클래스를 찾아보면 println / print / printf 메소드가 들어있다(정적 메소드 아님)
+ void println()이 메소드 오버로딩되어있다. ex) void println(int i), void println(char x) 등
우리가 쓰는 Scanner 객체 생성하고 안에 System.in 썼던 이유
- Scanner 클래스를 찾아보면 Scanner(InputStream source)라는 생성자 가 있다.
- Scanner 생성자의 매개변수가 InputStream 타입이라서
- Scanner 객체 생성할때 생성자의 매개변수가 System.in 이었다
- System.in의 리턴형을 보면 InputStream 이다
+ in은 static이 붙은 정적필드라서 System.in 이라고 쓴다.
'국비지원 과정 > JAVA' 카테고리의 다른 글
코딩 9일 / 2022.07.01 / JAVA의 문자열 처리 클래스(String, StringBuffer, StringTokenizer) (0) | 2022.07.11 |
---|---|
코딩 8일 / 2022.06.30 / JAVA의 싱글톤, final, 날짜시간 관련 클래스 (0) | 2022.07.07 |
코딩 6일 / 2022.06.28 / JAVA의 클래스,객체,필드,생성자 (0) | 2022.07.01 |
코딩 5일 / 2022.06.27 / JAVA의 배열2,참조형,클래스,객체 (0) | 2022.06.28 |
코딩 4일 / 2022.06.24 / JAVA의 제어문3,메소드,배열1 (0) | 2022.06.27 |