Java OOP
一、面向对象编程
面向对象是设计方法,它的基本思想是使用类、对象、继承、封装、消息等基本概念进行程序设计。
从现实世界中提取出事物的属性特征,把他们抽象成系统中的类。
访问修饰符:
访问修饰符 | 范围 |
---|---|
private | 私有,仅允许类内部访问 |
default | 默认,允许类内部访问以及同包下访问 |
protected | 受保护,允许类内部访问、同包下访问以及子类中访问 |
public | 公有,所有地方都能访问,包括不同包 |
类修饰符只有public
公有和default
默认
public class A {
private String a = "a";
String b = "b";
protected String c = "c";
public String d = "d";
private void f1(){
System.out.println("A private f1()...");
}
void f2(){
System.out.println("A default f2()...");
}
protected void f3(){
System.out.println("A protected f3()...");
}
public void f4(){
System.out.println("A public f4()...");
}
}
public class B extends A{
}
public class C extends A {
public void f1(){
//System.out.println(super.b); //属性b是默认的,子类无法访问
System.out.println(super.c); //属性c是受保护的,可以在子类中访问
}
public void f2(){
//super.f2(); //f2()方法是默认的,子类无法访问
super.f3(); //f3()方法是受保护的,可以在子类中访问
}
}
package com.hubu.oop.modifier;
import com.hubu.oop.C;
public class ModifierTest {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
//System.out.println(a.a); //属性a是私有的,只能在类内部访问
System.out.println(a.b);
System.out.println(a.c);
System.out.println(a.d);
System.out.println("-----------------------");
//System.out.println(b.a); //属性a是私有的,只能在类内部访问
System.out.println(b.b);
System.out.println(b.c);
System.out.println(b.d);
System.out.println("-----------------------");
c.print();
c.function();
}
}
/*
b
c
d
-----------------------
b
c
d
-----------------------
c
A protected f3()...
*/
package com.hubu.oop;
import com.hubu.oop.modifier.A;
import com.hubu.oop.modifier.B;
public class ModifierTest {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
//System.out.println(a.a); //属性a是私有的,只能在类内部访问
//System.out.println(a.b); //属性b是默认的,不能在不同包下访问
//System.out.println(a.c); //属性c是受保护的的,不能在不同包下访问
System.out.println(a.d);
System.out.println("-----------------------");
//System.out.println(b.a); //属性a是私有的,只能在类内部访问
//System.out.println(b.b); //属性b是默认的,不能在不同包下访问
//System.out.println(b.c); //属性c是受保护的的,不能在不同包下访问
System.out.println(b.d);
System.out.println("-----------------------");
c.print();
c.function();
}
}
/*
d
-----------------------
d
-----------------------
c
A protected f3()...
*/
二、封装
封装指的是将对象的实现细节隐藏起来,然后通过一些公用方法getter/setter
来暴露该对象的功能。
对象的状态信息都被隐藏在对象内部,外界无法直接操作和修改。对一个类或对象实现良好的封装,可以实
现以下目的:
-
隐藏类的实现细节;
-
让使用者只能通过getter/setter方法来访问数据,限制对成员变量的不合理访问;
-
可进行数据检查,从而有利于保证对象信息的完整性;
-
便于修改,提高代码的可维护性
public class Person {
private String name;
private String gender;
private int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "person{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
}
Person person = new Person("张三", "男", 20);
System.out.println(person.getName()); //张三
System.out.println(person.getGender()); //男
System.out.println(person.getAge()); //20
三、继承
继承是面向对象实现软件复用的重要手段,当子类继承
父类 后,子类作为一种特殊的父类,将直接获得父类被protected
和public
修饰的属性和方法,对于default
修饰的属性和方法只有同包下的子类能够继承。
Java是单继承的,不支持多继承,一个类只能有一个直接父类,因为当多个父类中包含相同的方法时,子类在调用该方法或重写该方法时会产生混淆。
public class Base {
private int n1 = 1;
int n2 = 2;
protected int n3 = 3;
public int n4 = 4;
public Base() {
System.out.println("Base()被调用...");
}
public Base(int n1, int n2, int n3, int n4) {
System.out.println("Base(int n1, int n2, int n3, int n4)被调用...");
this.n1 = n1;
this.n2 = n2;
this.n3 = n3;
this.n4 = n4;
}
public int getN1() {
return n1;
}
public void setN1(int n1) {
this.n1 = n1;
}
public int getN2() {
return n2;
}
public void setN2(int n2) {
this.n2 = n2;
}
}
public class Sub extends Base {
public Sub() {
//super(); //子类构造时默认调用父类的无参构造函数
System.out.println("Sub()被调用...");
}
public Sub(int n1, int n2, int n3, int n4) {
super(n1, n2, n3, n4); //可以指定调用父类的构造器
//this(); //在构造函数中this()和super()不能同时使用,因为它们都需要在构造函数第一行
System.out.println("Sub(int n1, int n2, int n3, int n4)被调用...");
}
public void print() {
//System.out.println(this.n1); //子类不能直接访问父类中的private成员变量
System.out.println(this.getN1()); //但是可以调用父类中的方法间接访问私有成员变量
System.out.println(this.n2);
System.out.println(this.n3);
System.out.println(this.n4);
}
}
public class Final extends Sub{
public Final() {
System.out.println("Final()被调用..."); //调用父类的构造函数时会层层向上直到Object
}
}
Sub sub = new Sub();
System.out.println("----------------------");
Sub sub1 = new Sub(100, 200, 300, 400);
System.out.println("----------------------");
sub.print();
System.out.println("----------------------");
sub1.print();
System.out.println("----------------------");
Final f = new Final();
/*
Base()被调用...
Sub()被调用...
----------------------
Base(int n1, int n2, int n3, int n4)被调用...
Sub(int n1, int n2, int n3, int n4)被调用...
----------------------
1
2
3
4
----------------------
100
200
300
400
----------------------
Base()被调用...
Sub()被调用...
Final()被调用...
*/
super:
调用属性和方法时,直接从父类向上查找
public class A {
protected String a = "a";
}
public class B extends A{
protected String b = "b";
}
public class C extends B{
protected String a = "1";
protected String b = "2";
protected String c = "c";
public void getA(){
System.out.println(a);
}
public void getB(){
System.out.println(b); //相当于this.b,从本类开始查找属性b,如果本类中存在则使用,如果没有才向父类层层向上查找
}
public void getC(){
System.out.println(c);
}
public void getSuperA(){
System.out.println(super.a); //直接向父类层层向上查找属性a
}
public void getSuperB(){
System.out.println(super.b);
}
}
C c = new C();
c.getA();
c.getB();
c.getC();
c.getSuperA();
c.getSuperB();
/*
1
2
c
a
b
*/
重载和重写:
- 重载发生在同一个类中,多个方法之间方法名相同、参数列表不同。重载与方法的返回值以及访问修饰符无关,即重载的方法不能根据返回类型进行区分。
- 重写发生在父类子类中,子类的方法名、参数列表必须与父类方法 相同。另外,返回值要小于等于父类方法,抛出的异常要小于等于父类方法,访问修饰符则要大于等于父类方法。还有,若父类方法的访问修饰符为private,则子类不能对其重写。
public class A {
//两个f1()方法构成重载
protected A f1(){
return null;
}
//子类无法重写private方法
private A f1(boolean flag)
{
if(flag)
{
return new A();
}
return null;
}
}
public class B extends A{
//重写父类方法, 返回值可以是原来的子类, 访问修饰符可以增大范围不能缩小
@Override
public B f1() {
return null;
}
}
public class C extends B{
//重写父类方法, 返回值可以是原来的子类, 访问修饰符可以增大范围不能缩小
@Override
public C f1() {
return null;
}
}
四、多态
多态指的是父类的引用指向子类的对象,但运行时依然表现出类的行为特征,这意味着同一个类
型的对象在执行同一个方法时,可能表现出多种行为特征。
编译类型和运行类型:
定义时等号的左边为编译类型,等号的右边为运行类型,编译类型在定义对象时就确定了,但运行类型是可
以改变的
public class Animal {
public void shut(){
System.out.println("动物在叫唤...");
}
}
public class Cat extends Animal{
@Override
public void shut() {
System.out.println("小猫喵喵叫...");
}
}
public class Dog extends Animal{
@Override
public void shut() {
System.out.println("小狗汪汪叫...");
}
}
//编译类型为Animal,运行类型为Cat
Animal animal = new Cat();
animal.shut();
//运行类变为Dog
animal = new Dog();
animal.shut();
/*
小猫喵喵叫...
小狗汪汪叫...
*/
向上转型和向下转型:
向上转型指父类的引用指向了子类的对象,编译器自动完成
向下转型指将父类的引用强转为子类的引用,之后可以调用子类的成员
public class Animal {
public void shut(){
System.out.println("动物在叫唤...");
}
}
public class Cat extends Animal{
@Override
public void shut() {
System.out.println("小猫喵喵叫...");
}
public void eat(){
System.out.println("小猫在吃鱼...");
}
}
public class Dog extends Animal{
@Override
public void shut() {
System.out.println("小狗汪汪叫...");
}
public void eat(){
System.out.println("小狗在吃骨头...");
}
}
Animal animal = new Cat();
animal.shut();
//animal.eat(); //animal编译类型为Animal,无法访问子类特有的成员变量和成员方法
Cat cat = (Cat) animal; //向下转型后可以访问
cat.eat();
System.out.println("-------------------------");
animal = new Dog();
animal.shut();
Dog dog = (Dog) animal; //向下转型后可以访问
dog.eat();
/*
小猫喵喵叫...
小猫在吃鱼...
-------------------------
小狗汪汪叫...
小狗在吃骨头...
*/
当父类和子类中存在同名的属性时,由调用者的编译类型决定
class A {
int n = 1;
}
class B extends A {
int n = 2;
}
A a = new B(); //a的编译类型为A
B b = new B(); //b的编译类型为B
System.out.println(a.n); //1
System.out.println(b.n); //2
instanceof:
判断对象的运行类型是否为XX类型或XX类型的子类型
class A {
}
class B extends A {
}
A a = new B(); //a的运行类型为B
A a1 = new A(); //a1的运行类型为A
System.out.println(a instanceof A); //true
System.out.println(a instanceof B); //true
System.out.println(a1 instanceof A); //true
System.out.println(a1 instanceof B); //false
动态绑定机制:
- 当调用对象方法时,方法会和该对象的运行类型进行绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明哪里使用
public class A {
int i = 10;
public int getI() {
return i;
}
public int f1(){
return i + 10;
}
public int f2(){
return getI() + 10;
}
}
public class B extends A {
int i = 20;
@Override
public int getI() {
return i;
}
@Override
public int f1() {
return i + 10;
}
}
//b的编译类型为A,运行类型为B
A b = new B();
//调用B类的f1()方法 ---> i + 10 = 20 + 10 = 30
System.out.println(b.f1()); //30
//B类中没有重写f2()方法,调用从A类继承的f2()方法 ---> getI()[动态绑定机制调用B类的getI()方法] + 10 = 20 + 10 = 30
System.out.println(b.f2()); //30
五、静态static
静态变量,又称为类变量,在类加载时创建,为该类的所有对象共享
静态方法中只能使用静态变量和静态方法,不能使用this
和super
关键字
静态变量和静态方法可以直接通过类名调用而不需要创建对象实例,也能使用对象调用
public class A {
int n = 1;
static String a = "a"; //静态变量
public static void setA(String a) {
//this.a = a; //静态方法中不能使用this关键字
A.a = a;
}
public static void f1(){
//n = 2; //静态方法中只能使用静态变量和静态方法
setA("1");
}
public void f2(){
n = 1; //普通方法中既能使用静态变量/方法也能使用普通变量/方法
setA("b");
}
}
public class B extends A {
public static void print(){
//System.out.println(super.a); //静态方法中不能使用super关键字
System.out.println(a);
}
}
A.setA("~");
System.out.println(A.a); //~ 类名调用静态变量
六、代码块
静态代码块只在类加载时调用一次,代码块在每次创建对象时调用一次
在静态代码块中只能使用静态变量和静态方法
类加载条件:
- 调用类中的静态变量和静态方法时会加载该类
- 创建子类对象时会加载父类对象
- 创建对象实例时会加载该类(包括反射)
public class A {
int n = getN();
static {
System.out.println("A类静态代码块被执行...");
}
{
System.out.println("A类普通代码块被执行...");
}
public A() {
System.out.println("A类的无参构造函数被调用...");
}
public int getN() {
System.out.println("A类的getN()方法被调用...");
return 1;
}
}
public class B extends A {
static int n = getNum();
static {
System.out.println("B类静态代码块被执行...");
}
{
System.out.println("B类普通代码块被执行...");
}
public static void print(){
System.out.println("B类的静态方法print()被执行...");
}
public B() {
System.out.println("B类的无参构造函数被调用...");
}
public static int getNum() {
System.out.println("B类getNum()方法被调用...");
return 1;
}
}
//首先会加载父类(初始化静态变量并调用静态方法),然后加载子类(初始化静态变量并调用静态方法),然后初始化父类的普通变量并调用父类的普通代码块,调用父类构造函数,然后初始化子类的普通变量并调用子类的普通代码块,最后调用子类的构造函数
B b = new B();
System.out.println("----------------------");
B b1 = new B();
/*
A类静态代码块被执行...
B类getNum()方法被调用...
B类静态代码块被执行...
A类的getN()方法被调用...
A类普通代码块被执行...
A类的无参构造函数被调用...
B类普通代码块被执行...
B类的无参构造函数被调用...
----------------------
A类的getN()方法被调用...
A类普通代码块被执行...
A类的无参构造函数被调用...
B类普通代码块被执行...
B类的无参构造函数被调用...
*/
七、final常量
被final修饰的变量叫做常量,用大写字母命名,赋值后就不能修改了
- 可以在声明时赋值
- 可以在构造函数中赋值
- 可以在代码块中赋值
被static和final修饰的变量只能在声明时(访问时不会导致类加载)或在静态代码块中进行赋值
被final修饰的方法,子类不能进行重写,但是可以调用
被final修饰的类,不能被继承
public class A {
final int i = 1; //声明时赋值
final int j;
final int k;
static final int m = 4; //声明时赋值
static final int n;
static {
System.out.println("A类的静态代码块执行...");
n = 5; //静态代码中赋值
}
{
k = 3; //代码块中赋值
}
public A() {
j = 2; //构造函数中赋值
}
public final void f1(){
}
}
public final class B extends A{
//f1()为final方法不能被重写
//public final void f1(){
//
//}
public void f(){
f1(); //调用A类的final方法
}
}
//B类为final类不能被继承
public class C /* extends B*/{
}
//访问static final修饰的变量不会进行类加载(没有运行静态代码块)
System.out.println(A.m); //4
八、抽象abstract
abstract修饰的类为抽象类,类中可以没有抽象方法,继承该类要么声明为抽象类或重写抽象方法
abstract修饰的方法为抽象方法,没有方法体,只能定义在抽象类中,抽象方法不能被以下修饰符修饰
- private 私有方法不能被子类重写
- static 静态方法不能被子类重写
- final 修饰的方法不能被子类重写
public abstract class A {
//抽象方法不能被private、final、static修饰
//private abstract void f1();
//public final abstract void f1();
//public static abstract void f1();
public abstract void f1();
}
public class B extends A {
//继承抽象类,实现抽象方法
@Override
public void f1() {
}
}
public abstract class C extends A {
}
九、接口
接口中的属性默认为public static final修饰,方法默认为public abstract修饰
jdk1.8以后接口中可以定义静态方法也可以进行默认实现
public interface A {
public static final int a = 1; //属性
//public abstract void f1(); //方法
//默认实现
default void f1(){
System.out.println(a);
}
//静态方法
static void f2(){
System.out.println("static: " + a);
}
}
public class B implements A { //实现接口A
//重写抽象方法
@Override
public void f1() {
System.out.println("B");
}
}
B b = new B();
b.f1(); //B
A.f2(); //static: 1
一个接口可以继承多个接口
public interface A {
}
interface J{
}
interface k extends A, J{
}
十、内部类
1.局部内部类
定义在类的方法或代码块中,作用域在方法或代码块中,其他位置不能访问,不能使用访问修饰符,可以用final修饰
public class Outer {
private int n1 = 100;
//代码块
{
System.out.println("Outer的代码块...");
class Inner1{
//与外部类重名属性
private int n1 = 200;
public void f3(){
System.out.println("Inner2 n1=" + n1); //访问重名遵循就近原则
//访问外部类重名属性使用外部类名.this.属性名
System.out.println("Outer n1=" + Outer.this.n1);
}
}
new Inner1().f3();
}
public void f1(){
System.out.println("Outer f1()...");
class Inner2{
//与外部类重名属性
private int n1 = 300;
public void f2(){
System.out.println("Inner2 n1=" + n1); //访问重名遵循就近原则
//访问外部类重名属性使用外部类名.this.属性名
System.out.println("Outer n1=" + Outer.this.n1);
}
}
new Inner2().f2();
}
}
Outer outer = new Outer();
outer.f1();
/*
Outer的代码块...
Inner2 n1=200
Outer n1=100
Outer f1()...
Inner2 n1=300
Outer n1=100
*/
2.匿名内部类
定义在类的方法或代码块中,作用域在方法或代码块中,不能使用访问修饰符
interface IA{
void m1();
}
class A {
public void f1(){
System.out.println("A f1()...");
}
}
public class Outer {
private static int n = 100;
public static void main(String[] args) {
//接口的匿名内部类
/*
class 匿名类(外部类全类名$数字) implements IA{
@Override
public void m1() {
System.out.println("匿名类实现m1()方法...");
}
}
*/
IA a = new IA() {
int n = 1;
@Override
public void m1() {
System.out.println(n); //访问重名遵循就近原则
System.out.println(Outer.n);
System.out.println("匿名类实现m1()方法...");
}
};
System.out.println(a.getClass());
a.m1();
//类的匿名内部类
/*
class 匿名类(外部类全类名$数字) extends A{
@Override
public void f1() {
System.out.println("匿名内部类重写f1()方法...");
}
}
*/
A a1 = new A() {
int n = 2;
@Override
public void f1() {
System.out.println(n); //访问重名遵循就近原则
System.out.println(Outer.n);
System.out.println("匿名内部类重写f1()方法...");
}
};
System.out.println(a1.getClass());
a1.f1(); //动态绑定
}
}
/*
class com.hubu.oop.innerclass.anonymous.Outer$1
1
100
匿名类实现m1()方法...
class com.hubu.oop.innerclass.anonymous.Outer$2
2
100
匿名内部类重写f1()方法...
*/
3.成员内部类
定义在类的成员位置,作用域为类作用域,可以使用访问修饰符,只能访问外部类的静态成员
public class Outer {
private int n;
class Inner{
private int n = 1;
public void f1(){
System.out.println(n); //访问重名遵循就近原则
System.out.println(Outer.this.n); //访问外部类重名属性使用外部类名.this.属性名
}
}
public Inner getInstance(){
return new Inner();
}
}
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.f1();
System.out.println("----------------------------");
Outer.Inner instance = outer.getInstance();
instance.f1();
/*
1
0
----------------------------
1
0
*/
4.静态内部类
定义在类的成员位置,被static修饰,作用域为类作用域,可以使用访问修饰符,只能访问外部类的静态成员
public class Outer {
private static int n;
static class Inner{
private int n = 1;
public void f1(){
System.out.println(n);
System.out.println(Outer.n);
}
}
public Inner getInstance(){
return new Inner();
}
}
Outer.Inner inner = new Outer.Inner();
inner.f1();
System.out.println("----------------------------");
Outer outer = new Outer();
Outer.Inner instance = outer.getInstance();
instance.f1();
/*
1
0
----------------------------
1
0
*/