一、复习
1、面向对象的基本特征
(1)封装:
- 目的或好处:隐藏内部的实现细节、对成员的访问是可控,只能按照其指定的暴露的方式来访问。提高了代码的维护性。
- 例如:属性私有化,如果没有提供 get/set,那么外部是不知晓有这个属性的。如果提供了 get/set 方法,可以在 get/set 方法写我们想要的逻辑的代码,如加条件判断(对 set 的属性值进行控制)、在 get 方法中也可以加计算。
(2)继承
- 目的或好处:代码的复用、代码的扩展、可以表示事物与事物之间的 is-a 关系。提高代码的扩展性。
- 代码的复用:父类声明过的成员,子类不需要重复声明。
- 代码的扩展:子类可以新增成员,或对父类的某些方法进行重写。
(3)多态(今天讲)
2、抽象类
抽象类的语法格式:
【其他修饰符】 abstract class 抽象类名{
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】); //抽象方法
}
- 如果一个类中有抽象方法,那么这个类一定是抽象类。
- 如果一个类没有抽象方法,那么这个类可以是抽象类,也可以不是抽象类。换句话说,抽象类中可以有抽象方法,也可以没有抽象方法。如果一个类没有抽象方法却声明为抽象类的话,就一个目的,为了让你创建它更具体的子类的对象。
3、接口
接口的语法格式:
【修饰符】 interface 接口名{
}
类实现接口的语法格式:
【修饰符】 class 类名 【extends 父类】 implements 父接口们{
}
接口继承接口的语法格式:
【修饰符】 interface 子接口名 extends 父接口们{
}
接口的成员:
JDK8 之前:
- 公共的静态的常量:public static final,它们可以省略
- 公共的抽象方法:public abstract,它们可以省略。非抽象的实现类一定要重写。
JDK8 之后:
- 公共的默认方法:public default,其中 public 可以省略。default 不可以省略。默认方法子类可以重写可以不重写。当实现类重写接口的默认方法时,default 要去掉。
- 公共的静态方法:public static,其中 public 可以省略。static 不可以省略。静态方法是不可以被重写的。
JDK9 之后:
- 私有的方法:private,private 不可以省略。这个方法只能在接口内部使用。
二、关键字
1、其中 private、protected、public、final、abstract、static、native 等这些都可以修饰方法,那么它们哪些可以搭配使用,哪些不可以?
final 的方法:方法不能被重写
- private:可以,但没必要同时 final 又 private。private 的方法不会被重写。
- 缺省:可以
- protected:可以
- public:可以
- abstract:不可以,矛盾的。
- static:可以,但没必要同时 final 又 static。static 的方法不能被重写。
- native:可以
abstract 的方法:抽象方法
- private:不可以。因为 abstract 必须被重写,而 private 的方法不能被重写。
- 缺省:可以
- protected:可以
- public:可以
- static:不可以。因为(1)静态方法不能被重写(2)如果通过抽象类名.静态抽象方法名时,没有方法体可以执行。
- native:不可以。因为 abstract 的方法表示方法体在子类中,native 的方法表示方法体在底层 C/C++中。
static 的方法:静态方法
- private:可以
- 缺省:可以
- protected:可以
- public:可以
- native:可以
2、private、protected、public、final、abstract、static、native 等哪些能修饰类(先不讨论内部类)
- private:不可以
- 缺省:可以
- protected:不可以
- public:可以
- final:可以。不能被继承,太监类。
- abstract:可以
- static:不可以
- native:不可以
- 不可以同时 final 和 abstract
3、private、protected、public、final、abstract、static、native 等哪些能修饰成员变量
- private:可以
- 缺省:可以
- protected:可以
- public:可以
- final:可以,但是要手动赋值
- abstract:不可以,没有抽象变量的说法。
- static:可以
- native:不可以
- static,final:可以。这种情况一般要大写。
4、private、protected、public、final、abstract、static、native 等哪些能修饰构造器
- 构造器前面只能加权限修饰符:private、缺省、protected、public
5、private、protected、public、final、abstract、static、native 等哪些能修饰局部变量
- 只能是 final
三、面向对象的基本特征之三:多态
3.1 多态的好处(重要)
- 同一个父类或父接口可以有不同的子类实现
- 主要体现为方法的重写(运行时多态)和方法的重载(编译时多态)
- 允许使用统一的父类或父接口处理不同子类对象,提高了程序的灵活性和扩展性。
3.2 什么是多态引用(重要)
当父类或父接口的变量,指向一个子类的对象时,就是多态引用。
语法格式:
父类或父接口的变量 = 子类的对象;
3.3 多态引用有什么不同(重要)
当调用方法
时,遵循编译时看左边,运行时看右边的原则,从而实现方法的动态绑定。
看右边:如果子类重写
了该方法,一定执行子类的。如果子类没有重写该方法,那么仍然执行父类声明的方法体。
package com.atguigu.polymophism;
public class Animal {//动物
public void eat(){
System.out.println("吃东西");
}
}
package com.atguigu.polymophism;
public class Dog extends Animal{//狗
@Override
public void eat() {
System.out.println("狗吃屎");
}
public void watchHouse(){
System.out.println("看家");
}
}
package com.atguigu.polymophism;
public class TestAnimal {
public static void main(String[] args) {
/* Dog d = new Dog();//本态引用
d.eat();
d.watchHouse();*/
Animal a = new Dog();//多态引用,父类的变量指向了一个子类的对象
a.eat();
// a.watchHouse();//编译报错,因为编译时看左边,a的左边是Animal类型,现在Animal类中没有watchHouse()方法,所以编译报错。
}
}
3.4 多态的应用(重要)
其他子类代码:
package com.atguigu.polymophism;
public class Cat extends Animal{//猫
@Override
public void eat() {
System.out.println("猫吃老鼠");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
package com.atguigu.polymophism;
public class Pig extends Animal{//猪
@Override
public void eat() {
System.out.println("啥都吃");
}
}
package com.atguigu.polymophism;
public class Bird extends Animal{
}
package com.atguigu.polymophism;
public class Husky extends Dog{//哈士奇
}
3.4.1 多态数组
用一个父类类型的数组,管理一组子类的对象。
package com.atguigu.polymophism;
public class TestAnimal2 {
public static void main(String[] args) {
//在一个数组中,统一管理Animal的不同子类的对象
/* Dog[] dogs = new Dog[3];
dogs[0] = new Dog();
dogs[1] = new Cat();//错误
dogs[2] = new Pig();//错误
*/
Animal[] animals = new Animal[3];
animals[0] = new Dog();//多态引用,因为animals[0]是Animal父类类型的,右边是Dog对象
animals[1] = new Cat();
animals[2] = new Pig();
//多态引用, a是Animal父类类型的,但是实际上 数组的元素分别是Dog、Cat、Pig类型的
for (Animal a : animals) {//增强for循环,即foreach
a.eat();
}
}
}
3.4.2 多态参数
用一个父类类型的形参,接收各种子类对象的实参。
package com.atguigu.polymophism;
public class TestAnimal3 {
public static void main(String[] args) {
Dog d1 = new Dog();
look(d1);//方法调用时,形参(Animal a)是 Animal类型,实参d1是Dog类型。实参是给形参赋值的
//Animal a = d1;
Cat c1 = new Cat();
look(c1);
Pig p1 = new Pig();
look(p1);
Bird b1 = new Bird();
look(b1);
}
//定义一个方法,可以看各种动物吃东西
//3个重载方法,这种实现方法,如果新增子类,需要新增方法。如果要删除某个子类,也要删除对应的方法。
/*public static void look(Dog d){
d.eat();
}
public static void look(Cat c){
c.eat();
}
public static void look(Pig p){
p.eat();
}*/
public static void look(Animal a){
a.eat();
}
}
3.5 向上转型与向下转型(重要)
向上转型:本来是子类的对象,但是却赋值给父类的变量,编译器
就会把它按照父类的类型进行处理,此时调用方法时,只能看父类的。
向下转型:当某个对象被赋值给父类或父接口的变量后,发生了向上转型,此时失去了调用子类新增的方法的能力,如果需要调用子类新增方法的能力,就必须将其向下转型。向下转型操作可能有风险,需要通过 instanceof 判断进行避免,避免 ClassCastException 异常。
package com.atguigu.polymophism;
public class TestClassCast {
public static void main(String[] args) {
//double d = 1;
Animal a = new Dog(); //向上转型
a.eat();//编译器把a定为Animal类型,只能调用Animal中有的方法,Animal没有子类有的方法,调不了
// a.watchHouse();//报错
//如果希望上面的对象调用Dog类中扩展的新方法,只能将其向下转型
//int a = (int)d;
/* Dog d = (Dog) a;//向下转型,需要强制进行
d.eat();
d.watchHouse();*/
/*Cat c = (Cat) a;//向下转型。编译时a以Animal类型处理,那么Animal是Cat的父类,所以可以向下转型。
//上面这句代码发生了运行时异常,它是ClassCastException类型转换异常。
// 因为实际上a指向的是Dog对象,Dog与Cat之间是兄弟关系,不能强转。
//即向下转型要小心,可能有风险。
c.eat();
c.catchMouse();*/
/* if(a instanceof Dog){//instanceof,判断a中的对象是不是Dog类型的实例对象
Dog d = (Dog) a;//向下转型,需要强制进行
d.eat();
d.watchHouse();
}else if(a instanceof Cat){
Cat c = (Cat) a;
c.eat();
c.catchMouse();
}*/
//这里有一个新特性:模式匹配
if(a instanceof Dog d){//instanceof,判断a中的对象是不是Dog类型的实例对象
d.eat();
d.watchHouse();
}else if(a instanceof Cat c){
c.eat();
c.catchMouse();
}
System.out.println("=============================");
//总结:当a变量中实际new的对象的类型 <= instanceof后面判断的类型,结果就是true
System.out.println(a instanceof Dog);//true
System.out.println(a instanceof Animal);//true
System.out.println(a instanceof Object);//true
System.out.println(a instanceof Cat);//false
System.out.println(a instanceof Husky);//false
}
}
3.6 分析和总结
当出现以下情况时,就会有多态的表现: 编译时看父类,运行时看子类
(1)继承和重写
(2)多态引用:父类的变量或数组装的是子类的对象
(3)调用可能被子类重写的方法
3.7 多态引用时调用成员变量问题(了解)
package com.atguigu.field;
public class Father {
public int a = 1;
}
package com.atguigu.field;
public class Son extends Father{
public int a = 2;
}
package com.atguigu.field;
public class TestFatherAndSon {
public static void main(String[] args) {
Father f = new Son();//多态引用
System.out.println("f.a = " + f.a);//f.a = 1
/*
多态引用后,如果不是调用方法,而是直接访问属性,那么不会遵循编译时看左边,运行时看右边的原则。
而是遵循:只看编译时类型。
*/
System.out.println("((Son)f).a = " + ((Son)f).a);//2
Son s = (Son) f;
System.out.println("s.a = " + s.a);//2
System.out.println("====================");
Son son = new Son();//本态引用
System.out.println("son.a = " + son.a);//2
System.out.println("((Father)son).a = " + ((Father)son).a);//1
Father father = son;//向上转型
System.out.println("father.a = " + father.a);
}
}
3.8 虚方法(了解)
虚方法:在 Java 中代表的就是可能被子类重写的方法。只要不是 final、static、private 都是虚方法。这么说的话,抽象方法是天生的虚方法。接口的默认方法也是虚方法。
在多态引用时,虚方法的调用比较复杂:
- 编译时看左边,看父类
- 如果存在重载的情况,根据实参找形参,并且实参看的也是编译时类型。
- 如果有实参类型与形参类型一样的,最匹配
- 如果实参类型没有与形参类型一样的,找实参类型 小于 形参类型的,可以兼容。
- 如果存在重载的情况,根据实参找形参,并且实参看的也是编译时类型。
- 运行时看右边
- 在父类中找到匹配方法之后,再看子类中是否有对匹配的方法进行重写的。
- 如果有重写刚刚匹配的方法的,就执行重写的。
- 如果没有重写刚刚匹配的方法的,就仍然执行刚刚匹配的方法。
package com.atguigu.virtual;
class MyClass{
public void method(Father f) {
System.out.println("father");
}
public void method(Son s) {
System.out.println("son");
}
}
class MySub extends MyClass{
public void method(Father d) {
System.out.println("sub--father");
}
public void method(Daughter d) {
System.out.println("daughter");
}
}
class Father{
}
class Son extends Father{
}
class Daughter extends Father{
}
public class TestVirtualMethod {
public static void main(String[] args) {
Father f = new Father();
Son s = new Son();
Daughter d = new Daughter();
MyClass my = new MySub(); //多态引用
my.method(f);//调用虚方法method。运行结果是sub--father
/*
(1)编译时看左边(父类)
my是MyClass类型,就在MyClass类中找可以匹配的方法。
f是实参,它是Father类型,它与哪个method方法的形参更匹配。
public void method(Father f) {
System.out.println("father");
}
(2)运行时看右边(子类)
my的=右边new的是MySub的对象,就要看MySub类中是否有对刚刚匹配的方法进行了重写。
如果有,就执行重写后的。如果没有就仍然执行刚刚匹配的方法。
这里有重写
public void method(Father d) {
System.out.println("sub--father");
}
执行重写后的。
*/
my.method(s);//调用虚方法method。运行结果是son
/*
(1)编译时看左边(父类)
my是MyClass类型,就在MyClass类中找可以匹配的方法。
s是实参,它是Son类型,它与哪个method方法的形参更匹配。
public void method(Son s) {
System.out.println("son");
}
(2)运行时看右边(子类)
my的=右边new的是MySub的对象,就要看MySub类中是否有对刚刚匹配的方法进行了重写。
如果有,就执行重写后的。如果没有就仍然执行刚刚匹配的方法。
这里没有重写
执行在父类中匹配的方法。
*/
my.method(d);//调用虚方法method。运行结果是
/*
(1)编译时看左边(父类)
my是MyClass类型,就在MyClass类中找可以匹配的方法。
d是实参,它是Daughter类型,它与哪个method方法的形参更匹配。没有最匹配的,但是可以找到兼容的。
public void method(Father f) {
System.out.println("father");
}
(2)运行时看右边(子类)
my的=右边new的是MySub的对象,就要看MySub类中是否有对刚刚匹配的方法进行了重写。
如果有,就执行重写后的。如果没有就仍然执行刚刚匹配的方法。
这里有重写
public void method(Father d) {
System.out.println("sub--father");
}
执行重写后的。
*/
Father b = new Son();
my.method(b);//调用虚方法method。运行结果
/*
(1)编译时看左边(父类)
my是MyClass类型,就在MyClass类中找可以匹配的方法。
b是实参,它是Father类型(编译是Father),它与哪个method方法的形参更匹配。
public void method(Father f) {
System.out.println("father");
}
(2)运行时看右边(子类)
my的=右边new的是MySub的对象,就要看MySub类中是否有对刚刚匹配的方法进行了重写。
如果有,就执行重写后的。如果没有就仍然执行刚刚匹配的方法。
这里有重写
public void method(Father d) {
System.out.println("sub--father");
}
执行重写后的。
*/
}
}
四、内部类
4.1 什么内部类
定义在类的内部的类。它可以分为 4 种小的类别:
- 成员内部类:定义在方法外
- 静态内部类:有 static
- 非静态内部类:没有 static
- 局部内部类:定义在方法内
- 有名字的局部内部类
- 匿名的局部内部类(用的最多)
4.2 匿名内部类
4.2.1 匿名内部类的语法(掌握)
语法格式:
new 父类名(){ //匿名子类的构造器调用的是父类的无参构造
//定义类的成员
}
new 父类名(实参列表){// 匿名子类的构造器调用的是父类的有参构造
//定义类的成员
}
new 父接口名(){ //匿名子类的构造器调用的是Object父类的无参构造
//定义类的成员
}
- 因为匿名内部类没有名字,因此在声明类的同时,就需要把这个类的唯一的对象直接创建好。即声明一个新的类与创建对象是同时进行的。
- 类的成员可以有成员变量、构造器、成员方法、内部类、代码块(一会儿再讲)。但是因为匿名内部类没有名字,所以构造器写不了。理论上可以内部类中再定义内部类,但是不会这么干。
- 匿名内部类不建议写到过于复杂,所以一般在匿名内部类中只会定义方法,而且通常是重写父类或父接口的抽象方法。
示例 1
package com.atguigu.inner.anymous;
public interface Flyable {//接口
void fly();//抽象方法
}
package com.atguigu.inner.anymous;
public class TestFlyable {
public static void main(String[] args) {
//接口不能直接new对象
// Flyable f = new Flyable();//错误,这里不能直接创建Flyable接口本身的对象。
//多态引用
//左边f1是Flyable父接口类型,右边是一个匿名的实现类的对象
Flyable f1 = new Flyable() {
@Override
public void fly() {
System.out.println("我要飞的更高!");
}
public void run(){
System.out.println("我要跑的很快!");
}
};
f1.fly();
//f1.run();//错误,因为编译时f1是Flyable类型,Flyable接口中没有run方法
/*
new Flyable() {
@Override
public void fly() {
System.out.println("我要飞的更高!");
}
public void run() {
System.out.println("我要跑的很快!");
}
}.run();*/
}
}
示例 2
package com.atguigu.inner.anymous;
public abstract class Father {//抽象类
private String name;
public Father() {
}
public Father(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void show();//抽象方法
}
package com.atguigu.inner.anymous;
public class TestFather {
public static void main(String[] args) {
//抽象类不能直接创建对象
// Father f = new Father();//错误,不能创建Father类本身的对象
//创建Father子类的对象,可以是匿名子类
Father f = new Father() {
@Override
public void show() {
System.out.println("hello" + getName());
}
};
f.show();
Father f2 = new Father("张三") {
@Override
public void show() {
System.out.println("hi" + getName());
}
};
f2.show();
}
}
4.2.2 匿名内部类的应用
为什么要用匿名内部类呢?
哪里需要接口的实现类对象,哪里声明匿名内部类实现这个接口,遵循高内聚的开发原则,也省略了给类取名字,减少了.java 文件的数量。
package com.atguigu.inner.anymous;
public interface MyComparator {//定义一个接口
int compare(Object o1, Object o2);
}
/*
这个方法可以比较2个对象的大小。但有统一的要求:
当o1对象 大于 o2对象,返回正整数;
当o1对象 小于 o2对象,返回负整数;
当o1对象 等于 o2对象,返回0;
*/
package com.atguigu.inner.anymous;
public class MyArrays {//模仿Arrays写数组工具类
//定义一个方法:可以为任意对象数组找最大值
/* public static Object findMax(Object[] arr){
Object max = arr[0];
for(int i=1; i<arr.length; i++){
if(arr[i] > max){//报错,因为 arr[i]和max里面是地址值,地址值不能比较大小
max = arr[i];
}
}
return max;
}*/
public static Object findMax(Object[] arr,MyComparator comparator){
Object max = arr[0];
for(int i=1; i<arr.length; i++){
//按照约定,arr[i]大于max就会返回一个正整数
if(comparator.compare(arr[i], max) > 0){
max = arr[i];
}
}
return max;
}
}
package com.atguigu.inner.anymous;
public class Circle {
private double radius;
public Circle() {
}
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
'}';
}
}
package com.atguigu.inner.anymous;
public class TestCircle {
public static void main(String[] args) {
Circle[] circles = new Circle[3];
circles[0] = new Circle(2.0);
circles[1] = new Circle(1.5);
circles[2] = new Circle(1.0);
MyComparator my = new MyComparator() {
@Override
public int compare(Object o1, Object o2) {
Circle c1 = (Circle) o1;
Circle c2 = (Circle) o2;
// return (int) (c1.getRadius() - c2.getRadius());//有瑕疵
if(c1.getRadius() > c2.getRadius()){
return 1;
}else if(c1.getRadius() < c2.getRadius()){
return -1;
}else{
return 0;
}
}
};
Object max = MyArrays.findMax(circles, my);
System.out.println("半径最大的圆:" + max);
}
}
package com.atguigu.inner.anymous;
public class Student {
private String name;
private int score;
public Student() {
}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
package com.atguigu.inner.anymous;
public class TestStudent {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("张三",85);
students[1] = new Student("李四",90);
students[2] = new Student("王五",75);
System.out.println("数组的元素:");
for (Student student : students) {
System.out.println(student);
}
//找最大值
// MyArrays.findMax(students, new MyComparator());//因为接口不能直接创建对象
MyComparator my = new MyComparator() {
@Override
public int compare(Object o1, Object o2) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
/*if(s1.getScore() > s2.getScore()){
return 1;
}else if(s1.getScore() < s2.getScore()){
return -1;
}else{
return 0;
}*/
return s1.getScore() - s2.getScore();
}
};
Object max = MyArrays.findMax(students, my);
System.out.println("最高分的同学:" + max);
}
}
4.2.3 分析
4.3 有名字的局部内部类(了解)
局部内部类可以使用外部类的私有成员,也可以使用外部类定义局部内部类的方法的局部变量,但是此局部变量必须是 final 的。
示例 1:
package com.atguigu.inner.local;
public interface Flyable {
void fly();
}
package com.atguigu.inner.local;
public class Plane implements Flyable{ //有名字的,独立的实现类
@Override
public void fly() {
// System.out.println("我要飞入云霄" + name);
//错误,因为Plane类不能用TestFlyable的私有成员
}
}
package com.atguigu.inner.local;
public class TestFlyable {
private static String name;//私有的内部成员,成员变量
public static void main(String[] args) {
int speed = 100;//局部变量,它必须是final的。JDK8之后,一旦它被局部内部类使用了,就会默认加final
//局部内部类,且是有名字,且它实现了Flyable接口
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("我要飞的更高" + name );
System.out.println("速度:" + speed);
//内部类,可以直接使用外部类的私有成员。
}
};
Bird b1 = new Bird();
Bird b2 = new Bird();
b1.fly();
b2.fly();
Plane p1 = new Plane();
Plane p2 = new Plane();
p1.fly();
p2.fly();
}
}
示例 2:
package com.atguigu.inner.local;
public class TestOuter {
public static void main(String[] args) {
Object obj = show();//多态引用,左边是父类Object类型,右边是Inner
//上面的语句结束了之后,show()出栈了,show()的a不在栈里面
System.out.println(obj.toString());//编译看左边Object,运行看子类Inner的toString
}
public static Object show(){
int a = 1;//a是show()的局部变量。它的生命周期:从调用show开始,到show结束
class Inner{
public String toString(){
return "a = " + a;
//此a实际上不是外面的a,编译器会在Inner类的内部,自动添加了一个 成员变量是a,初始化为此时外部方法局部变量a的值
//只有这样,当调用in对象的toString方法时,a的生命周期还在。
}
}
// a = 2;//不可以修改。如果能修改,就会导致 Inner的toString方法中a的值的歧义,到底是1还是2。
Inner in = new Inner();
return in;
}
}
4.3 成员内部类(只要能区分什么能访问什么不能访问即可)
静态内部类 | 非静态内部类 | |
---|---|---|
成员 | 成员变量、构造器、成员方法、内部类、代码块,与普通类一样 | 成员变量、构造器、成员方法、内部类、代码块,与普通类一样 |
使用外部类的成员(包括私有的) | 只能用外部类的静态成员 | 可以外部类的所有成员 |
外部类的方法中,哪些能用它们 | 外部类的所有方法都能用 | 只能在外部类的非静态方法中使用 |
在外部类的外面使用内部类的静态成员 | 外部类名.静态内部类名.静态成员 | 外部类名.非静态内部类名.静态成员 |
在外部类的外面使用内部类的非静态成员(一般需要避免) | 外部类名.静态内部类 对象名 = new 外部类名.静态内部类(); 对象名.非静态成员 | 外部类名 对象名 1 = new 外部类名(); 外部类名.非静态内部类 对象名 2 = 对象名 1.new .非静态内部类(); 对象名 2.非静态成员 |
当内部类与外部类的成员重名了,如何区别?
(1)与外部类的静态成员重名: 外部类名.静态成员
(2)与外部类的非静态成员重名:外部类名.this.非静态成员
总的原则:
(1)同一个类中,静态的不能直接访问非静态的
(2)跨类使用,使用静态成员,通过“类名.”,使用非静态成员,一定要先 new 对象,通过“对象.”
如何选择该定义为静态内部类还是非静态内部类呢?
原则:在这个成员内部类中,如果要用到外部类的非静态成员,那么这个成员内部类只能是非静态。
如果不会用到外部类的非静态成员,那么这个成员内部类可以是静态的,也可以是非静态。静态会更简单一些。
同等问题:如果选择一个方法定义为静态方法还是非静态方法?
如果在方法中,需要使用本类的实例变量等非静态成员,那么这个方法只能是非静态的。
如果在方法中,不需要使用本类的实例变量等非静态成员,那么这个方法可以是静态的,也可以是非静态。静态会更简单一些。
如何选择一个成员变量是静态还是非静态的?
如果这个成员变量的值是大家共享的,那么就应该是静态的。
如果这个成员变量的值是每一个对象独立的,那么就应该是非静态。
示例 1
package com.atguigu.inner.member;
public class Outer {//外部类
private int outA;
private static int outB;
class One{//非静态的成员内部类
public void show() {
System.out.println("outA = " + outA);
System.out.println("outB = " + outB);
}
public static void print(){
System.out.println("haha");
}
}
static class Two{//静态的成员内部类
public void show() {
// System.out.println("outA = " + outA);//Two类是静态的,不能直接使用外部类非静态的成员
System.out.println("outB = " + outB);
}
public static void print(){
System.out.println("xixi");
}
}
public static void outShow(){
/*One one = new One();//One是非静态的,outShow是静态的。同样静态方法不能直接使用本类其他非静态成员
one.show();*/
Two two = new Two();
two.show();
}
public void outPrint(){
One one = new One();
one.show();
Two two = new Two();
two.show();
}
}
package com.atguigu.inner.member;
public class TestOuter {
public static void main(String[] args) {
//这里调用One的show()
Outer outer = new Outer();
Outer.One one = outer.new One();
one.show();
//这里调用Two的show()
Outer.Two two = new Outer.Two();
two.show();
//跨类使用另一个类的成员时,使用对方的静态成员,用类名.,
// 使用对方的非静态成员,用对象.
// 这里调用One或Two的print方法
Outer.One.print();
Outer.Two.print();
}
}
示例 2
package com.atguigu.inner.member;
public class Wai {
private static int a = 1;
private int b = 2;
class Inner{
private static int a = 3;
private int b = 4;
public void show(){
System.out.println("外面a = " + Wai.a);
System.out.println("里面a = " + a);
System.out.println("外面的b = " + Wai.this.b);
System.out.println("里面的b = " + this.b);
//这里有2个this,有2个当前对象,一个是外部类的当前对象,一个是内部类的当前对象
//因为为了show方法,需要创建2个对象
}
}
}
package com.atguigu.inner.member;
public class TestWaiInner {
public static void main(String[] args) {
Wai w = new Wai();
Wai.Inner i = w.new Inner();
i.show();
}
}
五、代码块(了解)
类的成员有 5 个:
- 成员变量(重点)
- 构造器(重点)
- 成员方法(重点)
- 内部类(除了匿名内部类需要大家会写,其余能看懂即可)
- 代码块(简单了解)
5.1 静态代码块
语法格式:
【修饰符】 class 类名{
static{
//它就是静态代码块
}
}
有什么用?
- 它是用来在类加载时为静态变量初始化的。
执行的特点:
- 每一个类的静态代码块只会执行 1 次。
5.2 非静态代码块
语法格式:
【修饰符】 class 类名{
{
//它就是非静态代码块
}
}
有什么用?
- 它是辅助构造器在 new 对象时为实例变量初始化的。
执行的特点:
- 每次 new 对象时都要执行。new 一次执行一次。
5.3 示例代码
示例 1
package com.atguigu.block;
public class Demo {
static {
System.out.println("1、静态代码块");
}
{
System.out.println("2、非静态代码块");
}
public static void show(){
System.out.println("3、haha");
}
}
package com.atguigu.block;
public class TestDemo {
public static void main(String[] args) {
Demo.show();
Demo d1 = new Demo();
Demo d2 = new Demo();
}
}
示例 2
package com.atguigu.block;
public class Father {
public Father(){
System.out.println("5、父类的无参构造");
}
{
System.out.println("2,父类的非静态代码块");
}
static{
System.out.println("1、父类的静态代码块");
}
}
package com.atguigu.block;
public class Son extends Father{
public Son(){
System.out.println("6、子类的无参构造");
}
{
System.out.println("4,子类的非静态代码块");
}
static{
System.out.println("3、子类的静态代码块");
}
}
package com.atguigu.block;
public class TestSon {
public static void main(String[] args) {
Son s1 = new Son();
Son s2 = new Son();
}
//(1)静态先于非静态
//(2)父类先于子类
//(3)非静态代码块先于构造器
}