java笔记八——面向对象(三)
一、静态修饰符:static
static修饰符 : 最早出现在main方法中。只能修饰成员,不能写在方法的内部。
被static修饰的成员:静态成员变量和静态的成员方法。
静态修饰成员变量
static 修饰的成员变量,是被所有的对象共享的数据。没有被static修饰的成员变量,是每个对象的独享数据或者是特有数据。
public class Person {
String name;
static String country = "中国";
}
public class StaticTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "张三";
Person p2 = new Person();
p2.name = "李四";
//使用对象p1修改变量country的值
p1.country = "美国";
System.out.println(p2.country);
}
}
静态内存
静态成员内存的特点
- 静态成员跟随自己的类进入到元数据区(静态区域)
- 静态成员属于自己的类,不属于对象
- 静态成员进入内存后,赋默认值
- 静态成员变量的初始化实际早于对象
静态成员的调用方式
静态的内存图中,已经很明白了,静态属于自己的类,不是对象,静态的调用方式应该是类名.静态成员
Person.country ;//调用静态成员
public static void main(String[] args) {
System.out.println(Person.country);
Person p1 = new Person();
p1.name = "张三";
Person p2 = new Person();
p2.name = "李四";
//使用对象p1修改变量country的值
Person.country = "美国";
System.out.println(Person.country);
}
静态的调用方式是两种 : 类名调用,对象调用.。非静态成员调用只能对象。
静态成员调用方式只有类名.调用 ,非静态成员只能对象.调用。 对象.静态调用方式,会被javac编译为类名调用。
静态方法
① 静态方法调用格式:类名.方法名
② 静态方法中不能直接使用非静态成员。
为什么不能调用非静态成员?
静态是先人,非静态是后人。 静态出现在内存的时间早于非静态。
public class Person {
String name;
static String country = "中国";
/**
* 静态方法
*/
public static void eat(){
System.out.println("人在吃饭" + country);
System.out.println(name); //错误写法,不允许
}
}
静态内存优先于对象,在静态的方法中不能使用this和super
main方法
public static void main(String[] args){
}
main方法详解
- public 最大权限 : main方法的调用者是JVM
- static 无需对象,被类名直接调用,JVM启动的时候使用类名.main启动程序
- void 无返回值,调用者是JVM,方法的返回值都是返回给调用者,JVM不需要返回值,没有意义
- main 固定方法名称
- args 字符串的数组,JVM调用方法main必须传递参数,后期对JVM设置参数
二、final修饰符
final修饰符是最终的意思,不可改变。final可以修饰类,修饰方法,修饰成员变量,修饰局部变量。
final修饰类
被final修饰的类,称为最终类,不能被其他的类继承。无子类、 太监类。
学过的final类有哪些, String,System,Scanner。
public final class A{} //这个类A,不能出现子类,如果继承,直接报错
public class B extends A{} //错误,编译错误,最终类不能继承
final修饰方法
被final修饰的方法是最终方法,不能被子类重写。可以被调用。
一个类中的部分方法很完美,但是另一部分方法有待完成,设计为两个部分。完美的方法就是final 。
public class A{
public final void a(){} //方法不能被子类重写
}
public class B extends A{
public void a(){} //最终方法,不能重写
}
final修饰局部变量
变量定义在方法的内部,是局部变量, final修饰后,一次赋值,终身不改变,锁死了变量的值,可以看做是常量。
- final修饰的基本类型,锁死值
- final修饰的引用类型,锁死内存地址 (引用类型中的成员不受影响)
- final修饰了方法的参数,调用者传递值后,方法的参数值就锁死
public static void main(String[] args) {
/**
* Student student 对象存储的是内存地址
* final修饰后,固定住的,不可改变是student变量保存的地址
* 但是,Student对象中的成员,不受影响
*/
final Student student = new Student();
student.age = 20;
student.age = 30;
System.out.println(student.age);
final int[] arr = {1,2,3};//arr变量的值,固定为内存地址,不可改变
arr[1] = 200;
show(5);
}
public static void show(final int x){
x = 6; //final修饰,不可改变,报错
}
}
final修饰成员变量
成员变量的定义位置是在类中、方法外面。成员变量在内存中有默认值。final修饰成员变量的时候,锁住的不是内存默认值,而是我们程序人员手动的赋值。(如果我们不手动赋值,IDEA会提示错误)
-
final修饰成员变量,可以直接手动赋值:
int age = 0;
-
final修饰成员变量,可以使用构造方法:
public Student(int age){this.age=age;}
-
final修改成员变量,不可以使用set方法完成
- final修饰的成员变量,可以构造方法赋值,不能set方法赋值
- 构造方法在new对象的时候,执行一次,仅仅一次
- 可set方法,可以反复执行!!
public class Student {
final int age ;
public Student (int age ){
this.age = age;
}
/*public void setAge(int age){
this.age = age; // 编译报错
}*/
}
三、代码块
静态代码块
写在类中方法外面 : static{}
静态代码块的执行时机 : 只要使用了这个类的成员(new对象,调用静态方法,静态变量),静态代码块就会执行,而且就一次
静态代码块主要用于 : 数据库连接池 (C3P0,Druid)
JDBC注册数据库驱动程序,使用静态代码块
构造代码块
写在类中方法外面的 {}, 创建对象的时候运行,new一次,运行一次
局部代码块
写在方法内部的 {} 局部代码块,没有用
对象的初始化过程(父子类)
- 父类.class文件先进入内存
- 子类.class文件再进入内存
- 初始化父类的静态成员(变量,代码块,方法)
- 初始化子类的静态成员
- 运行父类的静态代码块
- 运行子类的静态代码块
- 运行父类的构造代码块
- 运行父类的构造方法
- 运行子类的构造代码块
- 运行子类的构造方法
public class Person {
static {
System.out.println("1.父类的静态方法");
}
public Person() {
System.out.println("4.父类的构造方法");
}
{
System.out.println("3.父类的构造代码块");
}
}
public class Student extends Person{
static {
System.out.println("2.子类的静态方法");
}
public Student(){
System.out.println("6.子类的构造方法");
}
{
System.out.println("5.子类的构造代码块");
}
}
public static void main(String[] args) {
new Student();
}
}
三、内部类
含义
在Java编程语言里,程序是由类(class)构建而成的。在一个类的内部也可以声明类,我们把这样的类叫做内部类。
例如:
class A{ //外部类,封闭类
class B{} //内部类,嵌套类
}
作用
- 实现了更好的封装,我们知道,普通类(非内部类)的访问修饰符不能为private或protected,而内部类可以。当我们将内部类声明为private时,只有外部类可以访问内部类,很好地隐藏了内部类。
- 内部类可以继承(extends)或实现(implements)其他的类或接口,而不受外部类的影响。
- 内部类可以直接访问外部类的字段和方法,即使是用private修饰的,相反的,外部类不能直接访问内部类的成员。
原理
内部类是一个编译时的概念,编译后会生成两个独立的class文件,如下:
public class Outer{
private String outerName = "outer";
class Inner{
private String innerName = "inner";
}
}
编译后生成的文件如下图:
编译后Outer.Inner被重命名为Outer$Inner,句点(.)被替换成了美元符号($)。
分类
Java内部类可分为成员内部类、局部内部类、匿名内部类、静态内部类。
成员内部类
① 成员内部类特点:
-
成员内部类可以看成是外部类的一个成员
-
在成员内部类中无法声明静态成员,但可以声明
static final int y = 5;
② 成员内部类中不能声明静态成员的原因:
我们知道加载类时,会先初始化静态成员,如果成员内部类有静态成员,那么内部类就会在外部类之前生成,而内部类是为外部类服务的,内部类在外部类之前就生成可能会脱离掌控。在实例化成员内部类时,成员内部类会持有一个外部类当前对象的引用,这样在成员内部类中就可以直接访问外部类的成员,即使是private修饰的。
③ 成员内部类的调用方式
公式 : 外部类名.内部类名 变量名 = new 外部类对象().new 内部类对象()
④ 举例:
public class Outer {
private String outerName = "outer";
// 外部类无法直接访问内部类的成员,需要实例化内部类对象
private Inner inner = new Inner();
// 定义内部类
public class Inner {
private String innerName = "inner";
public void show(){
System.out.println("内部类可以直接访问外部类的成员包括私有属性:" + outerName);
}
}
// 访问内部类中的方法
public void show(){
System.out.println("外部类需要先创建内部类对象才能访问内部类成员:" + inner.innerName);
}
}
public static void main(String[] args) {
// 创建外部类Outer的对象,借助Outer类中间接访问Inner类
Outer outer = new Outer();
outer.show();
// 直接访问Inner类中的方法
Outer.Inner inner = new Outer().new Inner();
inner.show();
}
运行结果:
外部类需要先创建内部类对象才能访问内部类成员:inner内部类可以直接访问外部类的成员包括私有属性:outer
局部内部类
① 局部内部类特点
- 局部内部类的使用和成员内部类的使用基本一致,只是局部内部类定义在外部类的方法中,就像局部变量一样,并不是外部类的成员。
- 局部内部类在方法外是无法访问到的,但它的实例可以从方法中返回,并且实例再不被引用之前会一直存在。
- 局部内部类也可以访问所在方法的局部变量、方法参数等,限制是局部变量或方法参数只有在声明为final时才能被访问。
② 举例:
public class Outer {
private String outerName = "outer";
public void show(final String str){ // 方法参数为final型
class Inner{
public void print(){
System.out.println(outerName + str);
}
}
// 创建内部类对象
Inner inner = new Inner();
inner.print();
}
public static void main(String[] args) {
// 创建外部类Outer的对象,借助Outer类中间接访问Inner类
Outer outer = new Outer();
outer.show(":lalala");
}
运行结果:
outer:lalala
匿名内部类
① 可以把匿名内部类想象成是没有类名的局部内部类,匿名内部类有以下特点:
1、匿名内部类不能有构造器,匿名内部类没有类名,肯定无法声明构造器。
2、匿名内部类必须继承或实现一个接口,指定给new的类型为匿名类的超类型,
3、匿名类不能有显示的extends或implements子句,也不能有任何修饰符。
4、匿名内部类和成员内部类、局部内部类一样,也不能声明静态成员。
② 格式:
new 接口或者父类(){
//重写抽象方法
};
③ 例子:
public interface Inner {
public abstract void show();
public abstract void print();
}
public static void main(String[] args) {
// 创建匿名内部类并用多态接收对象
Inner inner = new Inner() {
// 重写方法
@Override
public void show() {
System.out.println("重写的第一个方法");
}
@Override
public void print() {
System.out.println("重写的第二个方法");
}
};
// 调用重写后的方法
inner.show();
inner.print();
}
运行结果:
重写的第一个方法重写的第二个方法
静态内部类
① 静态内部类特点:
1、静态内部类位于类中方法外。和成员内部类的位置相同
2、静态内部类不同于前三种内部类,静态内部类不会持有外部类当前对象的引用,所以在静态内部类中无法访问外部类的非静态成员,可以这么说,静态内部类不依赖于外部类。
② 静态内部类的调用方式:
公式:外部类名.内部类名 变量名 = new 外部类.内部类()
③ 举例:
public class Outer {
private String outerName = "outer";
private static int id = 123;
private Inner inner = new Inner();
// 定义内部类
public static class Inner {
public void print1() {
// System.out.println(outerName); // 无法访问外部类的非静态成员
System.out.println(id);
}
public static void print2(){
System.out.println(id);
}
}
// 访问内部类中的方法
public void show(){
inner.print1();
}
}
public static void main(String[] args) {
// 创建外部类Outer的对象,借助Outer类中间接访问Inner类
Outer outer = new Outer();
outer.show();
// 直接访问Inner类中的方法
Outer.Inner.print2();
}
运行结果:
123
123
内部类中出现相同的变量名
①成员内部类举例
public class Outer {
String str = "外部类的变量";
class Inner{
String str ="内部类的变量";
public void show(){
String str = "内部类中方法的变量";
System.out.println("str = " + str);
System.out.println("this.str = " + this.str);
System.out.println("Outer.this.str" + Outer.this.str);
}
}
}
public static void main(String[] args) {
// 直接访问Inner类中的方法
Outer.Inner inner = new Outer().new Inner();
inner.show();
}
运行结果:
str = 内部类中方法的变量
this.str = 内部类的变量
Outer.this.str外部类的变量