java基础(day11)
目录
一.Static关键字
1. static 静态成员变量
2. static 静态方法
3. 接口中的常量和方法
4. static静态代码块
4.1. 静态代码块的声明
4.2. 静态代码块的作用
4.3. 执行顺序
5. 小结
二.Package包
1. package关键字
2. 包作用域
3. import关键字
5. 小结
三.作用域
1. public公共访问修饰符
2. private私有访问修饰符
3. protected保护访问修饰符
4. friendly
5. 访问修饰符总结
内部类
1. Inner Class 内部类
2. Anonymous Class 匿名类
3. Static Nested Class静态内部类
4. 小结
一.Static关键字
1. static 静态成员变量
一个class
类中定义的成员变量,我们称之为实例成员变量(成员变量)。实例成员变量的特点是,每个实例都有独立的成员变量,各个实例的同名成员变量互不影响。除此以外,还有一种成员变量,是使用static
关键字修饰的成员变量,称为静态成员变量:static field
。
实例成员变量在每个实例中都有自己的一个独立“空间”,但是静态成员变量只有一个共享“空间”,所有实例都会共享该成员变量。举个例子:
class Person {public String name;public int age;// 定义静态成员变量number:public static int number;public Person(String name, int age) {this.name = name;this.age = age;}
}
public class Main {public static void main(String[] args) {Person ming = new Person("Xiao Ming", 12);Person hong = new Person("Xiao Hong", 15);ming.number = 88;System.out.println(hong.number);hong.number = 99;System.out.println(ming.number);}
}
注:对于静态成员变量,无论修改哪个实例的静态成员变量,都是访问相同的内存空间:所有实例的静员态成员变量都被修改了,原因是静态成员变量并不属于实例。
内存图:
┌──────────────────┐
ming ──>│Person instance │├──────────────────┤│name = "Xiao Ming"││age = 12 ││number ───────────┼──┐ ┌─────────────┐└──────────────────┘ │ │Person class ││ ├─────────────┤├───>│number = 99 │┌──────────────────┐ │ └─────────────┘
hong ──>│Person instance │ │├──────────────────┤ ││name = "Xiao Hong"│ ││age = 15 │ ││number ───────────┼──┘└──────────────────┘
推荐用类名.静态成员变量来访问静态成员变量。可以把静态成员变量理解为描述class
本身的成员变量(非实例成员变量)。
不推荐用实例变量.静态成员变量去访问静态成员变量。因为在Java程序中,实例对象并没有静态成员变量。在代码中,实例对象能访问静态成员变量只是因为编译器可以根据实例类型自动转换为类名.静态成员变量来访问静态对象。
2. static 静态方法
用static
修饰的方法称为静态方法。调用实例方法必须通过一个实例对象,而调用静态方法则不需要实例对象,通过类名就可以调用。
public class Main {public static void main(String[] args) {// 通过类名调用静态方法Person.setNumber(99);// 通过类名调用静态成员变量System.out.println(Person.number);}
}class Person {// 静态成员变量public static int number;// 静态方法public static void setNumber(int value) {number = value;}
}
态方法在定义或使用时,应该注意:
- 静态方法属于
class
类级别的,而不属于实例对象。因此,静态方法内部,无法访问this
变量,也无法访问实例对象的成员变量; - 静态方法只能访问静态成员变量或静态方法;
- 普通方法内部,也可以访问静态成员变量或静态方法;
- 通过实例变量也可以调用静态方法或静态成员变量(这只是编译器自动帮我们把实例改写成类名而已),不推荐使用。所以,通常情况下,通过实例变量访问静态成员变量和静态方法,会得到一个编译警告;
- 静态方法经常用于工具类。例如:
-
Arrays.sort()
Math.random()
- 静态方法也经常用于辅助方法。注意到Java程序的入口
main()
也是静态方法。
3. 接口中的常量和方法
因为interface
是一个特殊的“抽象类”,所以它不能定义实例成员变量。但是,interface
是可以有“静态成员变量”的,并且静态成员变量默认为final
,所以这个“静态成员变量”其实是一个常量:
public interface Person {
public static final int MALE = 1;
public static final int FEMALE = 2;
}
实际上,因为interface
的成员变量只能是public static final
类型,所以我们可以把这些修饰符都去掉,上述代码可以简写为:编译器会自动把该成员变量变为public static final
类型。
interface接口中也可以有静态方法,只能使用接口名称直接调用。例如:
interface AutoSearch{
static void quickSearch(){
//....
}
}
public class Main {
public static void main(String[] args) {
// 调用接口的静态方法
AutoSearch.quickSearch();
}
}
注:
接口中的默认方法,必须使用“接口实现类”的对象,才能调用。
接口中的静态方法,必须通过“接口名.方法名()”才能进行调用。
4. static静态代码块
4.1. 静态代码块的声明
在Java
类中使用static
关键字和{}
声明的静态代码块,静态代码块在“类被加载”的时候运行,而且只运行一次,并且优先于各种代码块以及构造函数。如果一个类中有多个静态代码块,会按照书写顺序依次执行。
4.2. 静态代码块的作用
如果有些代码逻辑或对象,需要在项目启动的时候就执行或创建,这时候就需要静态代码块。比如一个项目启动需要加载的很多配置文件或者数据库连接池对象等,我们就可以都放入静态代码块中。
另外,静态代码块中,只允许调用静态方法或静态的成员变量,不允许调用普通方法或普通成员变量。
4.3. 执行顺序
静态代码块 > 构造代码块 > 构造函数 > 普通代码块
public class CodeBlock {static{System.out.println("静态代码块A");}{System.out.println("构造代码块1");}{System.out.println("构造代码块2");}static{System.out.println("静态代码块B");}public CodeBlock(){System.out.println("无参构造函数");}public void sayHello(){{System.out.println("普通代码块");}}public static void main(String[] args) {System.out.println("执行了main方法");new CodeBlock().sayHello();;System.out.println("---------------");new CodeBlock().sayHello();;}
}
如果存在继承关系,基本的执行流程是“先静态(父类 > 子类),再非静态(父类 > 子类)”的顺序关系。
public class Demo {public static void main(String[] args) {Son s1 = new Son();System.out.println();Son s2 = new Son();}
}// 父类
class Base{static {System.out.println("Base父类的静态代码块");}{System.out.println("Base父类的构造代码块");}public Base() {System.out.println("Base父类的无参构造方法");}public Base(int val) {System.out.println("Base父类的有参构造方法");}
}// 子类
class Son extends Base{static {System.out.println("Son子类的静态代码块");}{System.out.println("Son子类的构造代码块");}public Son() {System.out.println("Son子类的无参构造方法");}
}
5. 小结
- 使用
static
关键字修饰的成员变量,被称为静态成员变量,它属于所有实例“共享”的成员变量。 - 使用
static
修饰的方法,被称为静态方法。 - 静态方法或静态成员变量,建议通过类名进行访问。(不推荐使用实例对象访问)
- 静态方法只能可以调用静态的方法或成员变量,不能调用普通的方法或成员变量。静态方法无法访问
this
。 - 使用
static
修饰的代码块,被称为静态代码块。在静态代码块在“类被加载”的时候运行,而且只运行一次。 - 静态代码块中,只允许调用静态方法或静态的成员变量,不允许调用普通方法或普通成员变量。
- 执行流程:静态代码块 > 构造代码块 > 构造函数 > 普通代码块。
- 如果存在继承关系,则执行流程为:先“静态”(父类 > 子类),再“非静态”(父类 > 子类)
- 静态方法常用于工具类和辅助方法。
二.Package包
1. package关键字
在Java中,我们使用package
来解决名字冲突。Java定义了一种使用名字来命名空间的方式,称之为包:package
。一个类总是属于某个包,类名(比如Person
)只是一个简写,真正的完整类名是 包名.类名。
在定义class
的时候,我们需要在第一行声明这个class
属于哪个包。
- 小明的
Person
类存放在包ming
下面,因此,完整类名是ming.Person
-
package ming; // 声明包名mingpublic class Person { }
例如:
JDK的Arrays
类存放在包java.util
下面,因此,完整类名是java.util.Arrays
在Java虚拟机执行的时候,JVM只看完整类名,因此,只要包名不同,类就不同。包可以是多层结构,用.
隔开。例如:java.util
。没有定义包名的class
,它使用的是默认包(default package),非常容易引起名字冲突,因此,不推荐使用默认包(default package)。
包没有父子关系。java.util
和java.util.zip
是不同的包,两者没有任何继承关系。
2. 包作用域
位于同一个包的类,可以访问包作用域的字段和方法。不用public
、protected
、private
修饰的字段和方法就是包作用域。例如,Person
类定义在hello
包下面:
package hello;public class Person {// 包作用域:void hello() {System.out.println("Hello!");}
}
3. import关键字
// Person.java
package ming;// 导入完整类名:
import mr.jun.Arrays;public class Person {public void run() {Arrays arrays = new Arrays();}
}第二种
// Person.java
package ming;public class Person {public void run() {mr.jun.Arrays arrays = new mr.jun.Arrays();}
}
还有一种写法是使用import static
的语法,它可以导入一个类的静态字段和静态方法:
package main;// 导入System类的所有静态字段和静态方法:
import static java.lang.System.*;public class Main {public static void main(String[] args) {// 相当于调用System.out.println(…)out.println("Hello, world!");}
}
Java编译器最终编译出的.class
文件只使用完整类名,因此,在代码中,当编译器遇到一个class
名称时:
- 如果是完整类名,就直接根据完整类名查找这个
class
; - 如果是简单类名,按下面的顺序依次查找:
-
- 查找当前
package
是否存在这个class
; - 查找
import
的包是否包含这个class
;
- 查找当前
查找java.lang
包是否包含这个class
5. 小结
- java内建的
package
机制是为了避免class
命名冲突。 - JDK的核心类使用
java.lang
包,编译器会自动导入。 - JDK的其它常用类定义在
java.util.*
,java.math.*
,java.text.*
,……
。 - 包名推荐使用倒置的域名,并且由小写字母组成。例如
org.apache
。
三.作用域
1. public公共访问修饰符
定义为public
的class
、interface
可以被其他任何类访问:
package abc;
public class Hello {
public void hi() {
}
}
package xyz;class Main {void foo() {// Main可以访问HelloHello h = new Hello();}
}
定义为public
的field
、method
可以被其他类访问,前提是首先有访问class
的权限:
package abc;public class Hello {public void hi() {}
}
上面的hi()
方法是public
,可以被其他类调用,前提是首先要能访问Hello
类:
package xyz;class Main {void foo() {Hello h = new Hello();h.hi();}
}
一个.java
文件只能包含一个public
类,但可以包含多个非public
类。如果有public
类,文件名必须和public
类的名字相同。
2. private私有访问修饰符
定义为private的field、method无法被其他类访问:
package abc;public class Hello {// 不能被其他类调用:private void hi() {}public void hello() {this.hi();}
}
实际上,确切地说,private
访问权限被限定在class
的内部,而且与方法声明顺序无关。推荐把private
方法放到后面,因为public
方法定义了类对外提供的功能,阅读代码的时候,应该先关注public
方法
3. protected保护访问修饰符
protected
作用于继承关系。定义为protected
的字段和方法可以被子类访问,以及子类的子类:
package abc;public class Hello {// protected方法:protected void hi() {}
}
上面的protected
方法可以被继承的类访问:
package xyz;class Main extends Hello {void foo() {// 可以访问protected方法:hi();}
}
4. friendly
friendly
默认访问修饰符,也代表包作用域,是指一个类允许访问同一个package
的没有public
、private
修饰的class
,以及没有public
、protected
、private
修饰的字段和方法。
package abc;
// friendly权限的类:
class Hello {// friendly权限的方法:void hi() {}
}
只要在同一个包,就可以访问friendly
权限的class
、field
和method
。注意,包名必须完全一致,包没有父子关系,com.apache
和com.apache.abc
是不同的包。
package abc;
class Main {
void foo() {
// 可以访问package权限的类:
Hello h = new Hello();
// 可以调用package权限的方法:
h.hi();
}
}
5. 访问修饰符总结
friendly
(默认访问修饰符): 在同一包内可见,不使用任何修饰符。使用位置:类、接口、变量、方法。private
(私有访问修饰符) : 在同一类的内部可见。使用位置:变量、方法。public
(公共访问修饰符): 对所有类可见。使用位置:类、接口、变量、方法protected
(保护访问修饰符) : 对同一包内的类、或子类(子类可以不同包)可见。使用位置:变量、方法。
修饰符 | 当前类 | 同一包内 | 子类(同一包) | 子类(不同包) | 其他包 |
public | Yes | Yes | Yes | Yes | Yes |
protected | Yes | Yes | Yes | Yes | No |
friendly | Yes | Yes | Yes | No | No |
private | Yes | No | No | No | No |
内部类
在Java
程序中,通常情况下,我们把不同的类组织在不同的包下面,对于一个包下面的类来说,它们是在同一层次,没有父子关系:
java.lang
├── Math
├── Runnable
├── String
└── ...
1. Inner Class 内部类
如果一个类定义在另一个类的内部,这个类就是Inner Class:
class Outer {class Inner {// 定义了一个Inner Class}
}
上述定义的Outer
是一个普通类,而Inner
是一个Inner Class
,它与普通类最大的区别:Inner Class
的对象实例不能单独存在,必须依附于一个Outer Class
外部类的对象实例。示例代码如下:
public class Main {public static void main(String[] args) {Outer outer = new Outer("Nested"); // 实例化一个OuterOuter.Inner inner = outer.new Inner(); // 实例化一个Innerinner.hello();}
}class Outer {private String name;Outer(String name) {this.name = name;}class Inner {void hello() {System.out.println("Hello, " + Outer.this.name);}}
}
这是因为Inner Class
除了有一个this
指向它自己,还隐含地持有一个Outer Class
实例,可以用Outer.this
访问这个实例。所以,实例化一个Inner Class
不能脱离Outer
实例。
Inner Class
和普通Class
相比,除了能引用Outer
实例外,还有一个额外的“特权”,就是可以修改Outer Class
的private
字段,因为Inner Class
的作用域在Outer Class
内部,所以能访问Outer Class
的private
字段和方法。
// 外部类
public class Outer {// 成员变量private int value = 10;// 普通方法private void test() {System.out.println("外部类的test()");}// 静态代码块static {System.out.println("静态代码块:外部类Outer被加载");}// 内部类class Inner{private int value = 5;// 错误:因为内部类中不能定义静态代码块
// static {
// System.out.println("内部类Element被加载");
// }public void build() {System.out.println("内部类Element的build()方法被执行");// 通过“this”关键字调用内部类自己的成员变量System.out.println("内部类Element的成员变量value=" + this.value);// 通过“外部类.this”调用外部类对象的成员变量System.out.println("外部类Demo的成员变量value=" + Outer.this.value);// 调用所在外部类对象的方法test();}}
}
观察Java
编译器编译后的.class
文件可以发现,Outer
类被编译为Outer.class
,而Inner
类被编译为Outer$Inner.class
。
2. Anonymous Class 匿名类
还有一种定义Inner Class的方法,它不需要在Outer Class中明确地定义这个Class,而是在方法内部,通过匿名类(Anonymous Class)来定义。示例代码如下:
public class Main {public static void main(String[] args) {Outer outer = new Outer("Nested");outer.asyncHello();}
}class Outer {private String name;Outer(String name) {this.name = name;}void asyncHello() {Runnable r = new Runnable() {@Overridepublic void run() {System.out.println("Hello, " + Outer.this.name);}};new Thread(r).start();}
}
观察asyncHello()
方法,我们在方法内部实例化了一个Runnable
。Runnable
本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable
接口的匿名类,并且通过new
实例化该匿名类,然后转型为Runnable
。在定义匿名类的时候就必须实例化它,定义匿名类的写法如下:
Runnable r = new Runnable() {
// 实现必要的抽象方法...
};
匿名类和Inner Class
一样,可以访问Outer Class
的private
字段和方法。之所以我们要定义匿名类,是因为在这里我们通常不关心这个类的定义,并且该类仅在定义的位置一次性使用,比直接定义Inner Class
可以少写很多代码。
3. Static Nested Class静态内部类
最后一种内部类和Inner Class
类似,但是使用static
修饰,称为静态内部类(Static Nested Class
)
package test;
// 普通类
// public class Outer{
// [静态]成员常量 [静态]成员常量 构造方法 静态代码块 [静态]方法
// 静态内部类
// class Inner
public class Outer2 {private double pi = 3.14;private static String name = "张三";public Outer2(){System.out.println("外部类的无参构造方法");}static {System.out.println("这个是外部类的静态代码块");}public static void test(){System.out.println("这个是外部的test");}public void eat(){System.out.println("外部类的普通方法");}@Overridepublic String toString(){return "Outer{"+"pi="+pi+",name="+name+'\''+ "}";}static class Inner2{private double e = 2.7;private static String names = "张三";public Inner2(){System.out.println("内部类的无参构造方法");}public void test(){// 静态内部类不能访问外部类的非静态的成员变量,也不能出现Outer2.this// Outer2.this.pi = 3.1415;System.out.println("修改前:"+Outer2.name);Outer2.name = "李四";System.out.println("修改后:"+Outer2.name);// 静态内部类不能访问外部类的非静态的成员方法// System.out.pringln(Outer2.this.eat());// 调用外部类的成员方法// 访问外部类的静态test方法// Outer2.this.test();// 不能用,不能有thisOuter2.test();// 类名.方法名()System.out.println(this.toString());// 调用本类的toStringdosth();// 调用自己的静态方法}// 静态内部类中允许存在静态的方法public static void dosth(){System.out.println("内部类的静态的dosth");}// 内部类的toString@Overridepublic String toString(){return "Inner{"+"e="+e+'}';}}}
用static
修饰的内部类和Inner Class
有很大的不同,它不再依附于Outer
的实例,而是一个完全独立的类,因此无法引用Outer.this
,但它可以访问Outer
的private
静态字段和静态方法。如果把StaticNested
移到Outer
之外,就失去了访问private
的权限。
4. 小结
Java
的内部类可分为:Inner Class
(内部类)、Anonymous Class
(匿名类)和Static Nested Class
(静态内部类)三种。Inner Class
和Anonymous Class
本质上是相同的,都必须依附于Outer Class
的实例,即隐含地持有Outer.this
实例,并拥有Outer Class
的private
访问权限。Static Nested Class
是独立类,但拥有Outer Class
的private
访问权限。