- 1 包
- 2 static 关键字
- 2.1 static 修饰变量
- 2.2 static 修饰方法
- 2.3 static 修饰代码块
- 3 访问限制符
- 4 内部类
- 5 类和对象的内存布局
1 包
包可以说,就是目录,和磁盘上的目录是一一对应的。
导入包:
例如,scanner在使用时,我们就需要导入(import java.util.Scanner;),导入后才能正常使用。我们自己创建的包之间也可以这样导入,做法相同,都可以在输入时按Tab 键直接导入,或者用Alt + Enter 进行导入。
关于Scanner详细可看《Java : Scanner用法 干货 简明》。
2 static 关键字
2.1 static 修饰变量
如果类中的某个成员加上了static ,说明这个成员是一个 类属性/类方法;如果没有static ,说明这个成员是一个实例属性/实例方法。
例子:
public class Cat {
String name;
String gender;
public Cat(String name, String gender) {
= name;
this.gender = gender; //实例属性
public static int n = 0; //类属性
this.eat("鱼");
}
public void eat(String food){
System.out.println(name + "正在吃" + food);
}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat("糯米","公猫");
Cat cat2 = new Cat("粽子","母猫");
}
}
例子中,我们创建了两个Cat 类的对象,其中的name、和gender属性是实例属性,和对象相关。也就是不同的对象可以有不同的属性,一只是叫糯米的公猫,一只是叫粽子的母猫。
其中的n = 0 是类属性,和类相关和实例无关,我们可以通过 Cat.n 的方式访问并且修改n值。
Cat.n = 100;
System.out.println(Cat.n);
Cat 类就相当于模具,根据它会做出不同颜色、口味的猫抓点心,在这里是不同颜色、性别的猫。
2.2 static 修饰方法
public static void func(){
System.out.println("这是static 修饰的方法");
; // 错误!!!
}
Cat.func();
我们写一个被static 修饰的方法,然后进行调用,注意通过类Cat 进行调用,不需要 Cat cat = new Cat (“糯米”,“公猫”) 这样的创建实例,它是被static修饰的类相关的方法,不需要实例也可以进行调用。
然后,注意在static修饰的静态方法中,不能使用this,this指向当前的实例,依托于实例,而static修饰的方法和实例无关,只和类有关。同理,无法在static 方法中访问非 static的变量的方法。
2.3 static 修饰代码块
static {
//静态代码块
//静态代码块,只在类加载的时候执行一次
System.out.println("这是静态代码块。");
}
{
System.out.println("这是普通代码块。");
}
Cat cat = new Cat("糯米","公猫");
Cat cat2 = new Cat("粽子","公猫");
静态代码块,只在程序启动的时候(类加载的时候)执行一次。通过上述例子,我们不难看出,静态代码块只执行了一次,而普通代码块执行了两次,因为创建了两个实例,创建几个实例,普通代码块就执行多少次。
3 访问限制符
前提条件:两个类,一个类A,一个调用类A的类B。
1、public:修饰的成员可以被外部的类随意访问。
public class A {
public int num = 0;
}
public class B {
public static void main(String[] args) {
A a = new A();
System.out.println(a.num); // 能正确访问
}
}
2、private:只能在A类中访问到
(一个成员,能是private,就尽量使用private)
public class A {
private int num = 0;
// 只能在A 类中使用
public static void main(String[] args) {
A a = new A();
System.out.println(a.num);
}
}
public class B {
public static void main(String[] args) {
A a = new A();
System.out.println(a.num); // 不能正确访问
}
}
3、default:在同一个包中就可以访问到
public class A {
int num = 0;
}
4、protected :可以被同包的其他类访问,也可以被其他包的子类访问。
访问权限高低:
public > protected > default > private
4 内部类
一个类的定义在另一个类的里面。
1、普通内部类 / 成员内部类
public class A {
class Test{
public int num;
}
public void func() {
Test t = new Test();
t.num = 10;
}
public static void main(String[] args) {
Test test = new Test(); // 错误 !!!
}
}
内部类Test 作为外部类A 的成员,它就要依托于 this 被引用,而在外部类A的static 修饰的方法是类相关的方法,没有this ,导致无法实例化。
2、静态内部类(内部类前用 static)
静态内部类不依赖外部类的 this,可以随意创建。
public class Main {
static class Test{ //加 static
public int num;
}
public void func() {
Test t = new Test();
t.num = 10;
}
public static void main(String[] args) {
Test test = new Test(); // 正确
}
}
3、匿名内部类(相对比较常用)
public class Main {
static class Test{ //加 static
public int num;
}
public void func() {
Test t = new Test();
t.num = 10;
}
public static void main(String[] args) {
//匿名内部类
A a = new A(){
//定义属性和方法
};
}
}
匿名内部类,没有名字,是A类的子类(继承自A类)。
4、局部内部类
直接定义到方法的内部。
public class Main {
static void main(String[] args) {
Test test = new Test();
// 局部内部类
class Test{
}
}
}
5 类和对象的内存布局
1、前提条件:两个类,一个类Test,一个类Main,在类Main 中创建一个方法func2,该方法创建了Test 类的对象,并输出对象的num。在主方法中调用func2 。
public class Test {
public int num = 100;
}
public class Main {
public static void func2(){
Test t = new Test();
System.out.println(t.num);
}
public static void main(String[] args) {
func2();
}
}
首先,程序运行找主方法,主方法进栈,然后主方法中调用func2方法,func2方法进栈,在func2方法中创建了对象。
在创建对象的时候,新创建出来的对象在 堆 上面的 0x100位置,然后栈中的引用存着它的引用 t 0x100,引用指向堆上的实例。关于引用以及具体的入栈出栈《Java笔记(三) —— 方法调用/入栈 出栈 栈帧/重载问题》《Java笔记 (五)—— 引用类型》 2、前提条件:一个 Main 类,一个 B 类,一个 A 类。Main 类中有方法func2 创建了类 B 的对象,并在主函数中调用;类 B中创建了类 A 的实例作为成员;类 A 中创建了String 的实例 " hello " 作为成员 。
public class Main {
public static void func2(){
B b = new B();
}
public static void main(String[] args) {
func2();
}
}
public class B {
public A a = new A();
}
public class A {
tring str = "hello";
}
(1) 程序运行,从主方法开始,主方法进栈;
(2) 主方法调用func2方法,func2方法进栈;
(3) func2 方法根据类 B 创建了对象(实例),创建出来的 B 的实例 b 在堆上的 0x100,栈帧中存放着实例的引用 b,该引用保存着实例在堆上的地址 0x100,指向在堆上的实例 b;而实例 b 中根据类 A 创建了实例,创建出来的 A 的实例 a 在堆上的 0x200,实例 b 中存放着实例 a 的地址0x200;
(4) 实例 a 创建了 String 的实例,创建出来的实例 str 在堆上的0x300,实例 a 中存放着 String 实例的地址 0x300;
(5) 堆上的0x300 放着创建的 String 的实例 " hello "。
3、上面两个例子讲了栈和堆,还有一个很重要的区域:方法区。方法区里存着的是一个一个 “ 类相关的信息 ”(每个类的方法的二进制的指令也是在这里)
(1) 如果属性是一个实例属性,那么不在方法区,是跟着实例走的,实例一般在 堆上;
(2)如果属性是一个类属性,那么就在 方法区;
(3)对于方法来说,不管是加 static 还是不加,对应的内容都在方法区。
前提条件:一个Main 类,一个 A 类,Main 类的主方法创建了类 A 的实例。
public class Main {
public static void main(String[] args) {
A a = new A();
}
}
public class A {
static public int num = 100;
static public String str = "hello";
public int num2 = 200;
public void func(){
//
}
}
main方法入口,main方法进栈,然后main方法创建了实例 a,该实例的地址存在栈帧的引用 a 中 0x100,实例存在堆上,num2 = 200为实例属性跟着实例走,所以在堆 上;A 类中的方法func 存在方法区中; A 类中的其他属性因为被 static 修饰为类属性,所以在方法区中,但注意static 修饰的String ,是将引用 str 0x200 存在方法区,由String 创建的实例str 在堆上。
看明白了会发现,有点意思,冲冲冲~