1 访问权限
- 修饰符:(√:可访问/×:不能访问)
修饰符 | 当前类 | 同一个包 | 子孙类(包内/外) | 其他包 |
---|---|---|---|---|
public(公共的) | √ | √ | √ | √ |
protected(受保护的) | √ | √ | √ | × |
default/friendly(默认的) | √ | √ | × | × |
private(私有的) | √ | × | × | × |
protected在包外有继承关系的子类中可以访问。
- 各自能修饰什么:(√:可修饰/×:不可修饰)
修饰符 | 类 | 成员(属性) | 方法 | 局部变量 |
---|---|---|---|---|
public(公共的) | √ | √ | √ | × |
protected(受保护的) | × | √ | √ | × |
default/friendly(默认的) | √ | √ | √ | × |
private(私有的) | × | √ | √ | × |
-
总结:
- public/default:可修饰类/成员/方法
- protected/private:成员/方法
-
为什么局部变量不需要访问权限修饰符修饰?
因为局部变量本身象征一个访问权限:只能在局部调用。也就是说在局部变量的生命周期中,除这个方法外,外界无法访问。所以不需要用任何修饰符修饰,也不能加static,但可以加final。 -
包的含义是文件系统里的文件夹,同一个包也就是在同一个文件夹下。
2 static
2.1 静态成员
- static能修饰什么?(√:可修饰/×:不可修饰)
修饰符 | 类 | 成员(属性) | 方法 | 局部变量 |
---|---|---|---|---|
static | × | √ | √ | × |
-
Java类加载的过程:
- 加载静态成员/代码块(先递归地加载父类的静态成员/代码块,再依次加载到本类的静态成员。同一个类里的静态成员/代码块,按代码的前后顺序加载。 如果其间调用静态方法,则调用时会先运行静态方法,再继续加载)
- 加载非静态成员/代码块(先递归地加载父类的非静态成员/代码块,再依次加载到本类的非静态成员。同一个类里的非静态成员/代码块,按代码的前后顺序加载)
- 调用构造方法(递归地调用父类的构造方法,先父后子)
-
类点属性或者类点方法时,类加载只进行了第一步。当这个类被new时,第二、三步才会执行,此时就不算类第一次被加载了。(在 2.3 加载顺序细说)
-
静态成员与类相关,非静态成员与对象相关
-
静态属性
特点:整个类型共享一份的属性而不是每个对象都有一份的属性,可以使用类名直接调用。(普通属性:每个对象都有一份,需要使用实例化后的引用调用)
-
静态方法
特点:可以使用类名调用,静态方法里面只能直接访问类的静态成员,如果访问非静态成员,需在类中创建其对象调用。
-
需要厘清逻辑:因为静态成员在类刚加载时就已经加载了,此时普通成员都还没有加载,所以无法访问到。 但是可以在普通方法中访问静态的类成员。
-
static为什么不能修饰局部变量
static修饰的变量要求类一加载就要找到,但是局部变量在方法被调用的时才会开辟内存空间。类的加载永远在前面,方法调用永远在后面,这两个时间点赶不上一起,于是static不能修饰局部变量。就算是静态方法中的局部变量也不行。 -
既然静态方法调用简单,为什么不把一个类里所有方法都定义成静态的?
静态方法里只能直接访问静态成员,如果在静态的方法里面访问非静态成员,需在类中创建其对象调用。 -
静态方法不可能出现this,因为静态方法与类相关,用类调用时无法匹配this所说的对象。
-
方法 优点 缺点 普通方法 普通方法里面既可以直接访问普通、静态成员 普通方法调用比较复杂,需要创建并使用对象调用 静态方法 静态方法调用简单,直接使用类名.方法 静态方法里面只能直接访问静态成员,如果想在静态的方法里访问非静态成员,需在类中创建其对象调用 -
访问非静态成员,需在类中创建其对象调用*
- 在静态方法中实例化这个类后调用
- 在静态方法的参数列表中传入普通成员的类,调用静态方法前先实例化这个普通成员的类并传入这个引用。
public class Test4{
static int i=0;
public int aMethod(){
i++;
return i;
}
public static void main(String[] args){
Test4 test4 = new Test4();
test4.aMethod();//1
int j = test4.aMethod();//2
System.out.println("j=" + j);//--->2
}
}
2.2 代码块
使用代码块进行属性的初始化。(初始化:定义变量并赋值)
名称 | 作用 | 份数 | 何时执行 | 执行次数 |
---|---|---|---|---|
普通代码块 | 初始化普通属性 | 每个对象都有一份 | 创建对象时(new XXX)执行 | 创建几个对象执行几次 |
静态代码块 | 初始化静态属性 | 整个类型共享一份 | 类第一次被加载的时候执行 | 只执行一次 |
class A{
//下面两句编译报错,i=45是赋值语句,语句只允许出现在方法中,不能出现在类中。类中只允许出现属性、方法。
int i;
i = 45;
//这样初始化变量i
int i = 45;
//底层会执行如下代码:
int i;
//代码块:初始化普通属性,创建对象的时执行
//每创建一个对象时执行一次,每个对象都有一份
{
i = 45;
}
static int i;
{
//因为普通代码块在创建对象时调用,
//如果直接使用类名调用变量i因没有初始化对象,调用A.i打印默认值0,
//此时应使用静态代码块
i = 45;
}
int i;
//静态代码块:初始化静态属性,类第一次被加载的时候执行
//只在类第一次加载时执行,整个类型共享一份
static {
//A.i 输出45
i = 45;
}
}
2.3 加载顺序
静态代码块中的代码从上向下依次执行,所有的大括号可以从上向下合并为一个来看,非静态同理。
//1 不存在继承关系且直接创建对象时的加载顺序
public class Test1{
public static void main(String[] args){
//第一次读到A类时,先加载A.class,此时执行静态代码块static{}
//第二步new A,创建了对象,所以第二步执行普通静态代码块
//第三步构造代码为收尾工作
A a = new A();
}
}
class A{
public A(){
System.out.println("A类构造方法");//S3
}
{
System.out.println("A类普通代码块");//S2
}
static{
System.out.println("A类静态代码块");//S1
}
}
/*
输出顺序
A类静态代码块
A类普通代码块
A类构造方法
静态代码块:当类第一次被加载的时候执行
普通代码块:当创建对象的时候执行
构造方法:当创建对象的时候执行(收尾工作)
*/
//1 存在继承关系且直接创建对象时的加载顺序
public class Test2{
public static void main(String[] args){
//第一步加载B.class,发现extends A,加载A.class
//第二步加载A的静态代码块,然后加载B的静态代码块
//第三步执行new B,创建B对象,在创建B对象时,构造方法中的super()优先级高于普通代码块和构造方法
//第四部执行构造方法中super(),递入父类,父类也会执行Object类的super(),但是这里不作了解,这一步也不会被打印
//第五步执行父类中普通代码块、构造方法
//第六步归回子类B,执行子类普通代码块、构造方法
B b = new B();
}
}
class A{
public A(){
System.out.println("A类构造方法");
}
{
System.out.println("A类普通代码块");
}
static{
System.out.println("A类静态代码块");
}
}
class B extends A{
public B(){
super();
System.out.println("B类构造方法");
}
{//5
System.out.println("B类普通代码块");
}
static{
System.out.println("B类静态代码块");
}
}
/*
输出顺序
A类静态代码块
B类静态代码块
(先执行B类的super(),递入父类A类)
A类普通代码块
A类构造方法
B类普通代码块
B类构造方法
注意:当执行完静态方法,创建对象时
先执行B类构造方法中的super(),
再到执行A类的普通代码块。
*/
//3 不存在继承关系且不创建对象时的加载顺序(如果存在继承不创建对象,只执行父类、子类的静态成员)
public class Test3{
public static void main(String[] args){
//第一次读到A类时,先加载A.class,此时执行静态代码块static{}
//与Test1不同的是,这里没有new一个新对象,所以不会加载普通代码块和构造方法
//A.test()和A.a,静态成员加载顺序按照书写的前后顺序执行
A.test();
System.out.println(A.a);
}
}
class A{
static int a = 40;
public A(){
System.out.println("A类构造方法");//S3
}
{
System.out.println("A类普通代码块");//S2
}
static{
System.out.println("A类静态代码块");//S1
}
static void test(){
System.out.println("静态方法");
}
}
/*
输出顺序
A类静态代码块
静态方法
40
*/
-
例题
- 分析这种题可以将普通成员和静态成员的变量拆分成代码块的形式。
- 注意:super.xxx调用的是父类的成员不会被是否重写影响。super()是直接“复制”了父类的内容直接来到子类使用,代码共享。
public class Example1{
public static void main(String[] args){
//A -> A.class -> static{} -> y = 20 -> 50
//忽略非静态成员和构造方法
System.out.println(A.y);//--->50
}
}
class A{
int x = 10;
static int y = 20;
int x;
{
x = 10;
}
{
x = 30;
y = 40;
}
static{
y = 50;
}
public A(){
x = 60;
y = 70;
}
}
public class Example2{
public static void main(String[] args){
//创建了对象,还有构造方法,只需要看构造方法即可
A aa = new A();
System.out.println(aa.x);//--->60
System.out.println(A.y);//--->70
}
}
class A{
int x = 10;
static int y = 20;
int x;
{
x = 10;
}
{
x = 30;
y = 40;
}
static{
y = 50;
}
public A(){
x = 60;
y = 70;
}
}
public class Example3{
public static void main(String[] args){
//执行普通代码块
//执行构造方法,构造方法中执行test(),输出a,与B类没什么关系
A aa = new A();//--->a
}
}
class A{
String str = "a";
public A(){
test();
}
public void test(){
System.out.println("A类的" + str);
}
}
class B{
String str = "b";
public B(){
//super();默认执行
test();
}
@Override
public void test(){
System.out.println("B类的" + str);
}
}
/*输出:
A类的a
*/
===============================================
public class Example4{
public static void main(String[] args){
//结合Test2的结论,在加载完静态变量之后,直接去B类加载super()
//加载进来A类构造方法的内容test()
//但是并不是要在A类执行,相当于只是复制了代码来B执行,此时变量str未加载,所以输出为null
//执行完普通代码块再执行构造方法时,可以输出b
B b = new B();
}
}
class A{
String str = "a";
public A(){
test();
}
public void test(){
System.out.println("A类的" + str);
}
}
class B{
String str = "b";
//当涉及到子类方法一定注意方法体如果首行没有super()或者this(),先补一个
//直接调用B类时先读B类构造方法,super()使构造方法递到父类构造方法,执行后再归下来继续执行其他方法
public B(){
//super();默认执行
test();
}
@Override
public void test(){
System.out.println("B类的" + str);
}
}
/*输出:
B类的null
B类的b
*/
3 final
最终的,也被称为“骡子类”。修饰类时,该类不允许被继承;修饰方法时,该方法不允许被重写,但是允许重载,允许被继承;修饰变量时,该变量是最终变量/常量,不允许被重新赋值。
- final能修饰什么?(√:可修饰/×:不可修饰)
修饰符 | 类 | 成员(属性) | 方法 | 局部变量 |
---|---|---|---|---|
final | √ | √ | √ | √ |
-
为什么Sun公司在定义String类和Math类的时候都用final修饰的?
- String类代表字符串类,是项目开发里面的很基础,核心类。越是基础、核心的类,所有的程序员越应该保持一致。
- Math类表示数学类,里面定义的都是公理、定理,不能按照程序员的意愿随意的进行修改。
-
注意: final修饰的基本数据类型,其数值不变(报错无法为最终变量x分配值);final修饰的引用数据类型,其地址不变。
public class Test{
public static void main(String[] args){
final Student stu = new Student("张三",22);
stu.name = "张三丰";
System.out.println(stu.name + " " + stu.age);
}
}
class Student{
String name;
int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
}
4 abstract
抽象的。父类定义了方法有内容但因为太抽象大多都需要继承重写使用,写一些内容也没什么意义。但不定义这个方法又不符合java的继承特点,定义为抽象方法可以解决这个问题。抽象类中既可以有抽象方法,又可以有普通方法供子类进行代码共享提高代码重用率。
-
abstract的特点?
不能有方法实现,省去方法体。
-
abstract能修饰什么?(√:可修饰/×:不可修饰)
修饰符 | 类 | 成员(属性) | 方法 | 局部变量 |
---|---|---|---|---|
abstract | √ | × | √ | × |
-
抽象类:表示这个类型不形象不具体,不能创建对象。
-
抽象方法:表示这个类型一定会这个方法,但是现在给不出明确定义,待留给子类去实现。不能有方法实现(方法体)。
-
一个类里面只要出现了抽象方法,那么这个类一定要定义成抽象类。
-
抽象类里面既可以定义抽象方法,又可以定义普通方法。
-
抽象类有构造方法(Java中只要是类就一定有构造方法),其作用是?
抽象类是类,但是不能创建创建对象,这里的构造方法是用来给子类构造方法首行的super()使用。
public class Test{
public static void main(String[] args){
Cat c = new Cat();
c.sleep();
c.eat();
}
}
abstract class Animal{
public abstract void eat();
public void sleep(){
System.out.println("都需要睡觉");
}
}
class Cat extends Animal{
@Override
public void eat(){
System.out.println("猫吃鱼");
}
}
class Dog extends Animal{
@Override
public void eat(){
System.out.println("狗吃骨头");
}
}
/*输出
都需要睡觉
猫吃鱼
*/
- abstract和final能不能同时修饰一个方法?
不能,因为abstract修饰的方法表示这个类型一定会这个方法,但是现在给不出具体的实现,留给子类去重写实现。final修饰的方法表示最终方法,可以被继承,但不能被重写。