面向对象
创建对象的过程
- 在第一次遇到一个类的时候,对这个类要进行加载,只加载一次。
- 创建对象,在堆中开辟空间
- 对对象进行初始化操作,属性赋值都是默认的初始值
- new关键字调用构造器,执行构造方法,在构造器中对属性进行重新赋值
this 的使用
this指代的是当前的对象
this关键字用法
- this可以修饰属性: 注意就近原则
public class Person {
private String name;
private int age;
private double height;
// this 在构造器中的使用
public Person(String name, int age,double height) {
this.name = name;
this.age = age;
this.height = height;
}
public Person(){
}
public void eat(){
int age = 10;
System.out.println(age);// 就近原则 age指的是局部变量的age,而不是成员变量的age
System.out.println(this.age); // this.age 指代类的属性age
System.out.println("I like eat");
}
public static void main(String[] args) {
Person p = new Person("lili",12,160.5);
p.eat();
}
}
- this 修饰方法
public void eat(){
int age = 10;
System.out.println(age);// 就近原则 age指的是局部变量的age,而不是成员变量的age
System.out.println(this.age); // this.age 指代类的属性age
System.out.println("I like eat");
}
public void play(){
this.eat();
System.out.println("玩");
}
- this 修饰构造器
public class Person {
private String name;
private int age;
private double height;
// this 在构造器中的使用
public Person(String name, int age,double height) {
this(name,age); // this修饰构造器必须放在第一行
this.height = height;
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public Person(){
}
}
static 的使用
static可以修饰:属性,方法,代码块,内部类
static 修饰属性
public class Test {
int id;
static int sid;
public static void main(String[] args) {
Test t1 = new Test();
t1.id = 1;
t1.sid = 10;
Test t2 = new Test();
t2.id = 2;
t2.sid = 20;
Test t3 = new Test();
t3.id = 3;
t3.sid = 30;
}
}
内存分析
一般官方的推荐static修饰属性的访问方式
// 通过类名.属性名的方式进行访问
Test.sid = 100;
static修饰属性总结:
- 在类加载的时候一起载入方法区中的静态域中
- 先于对象进行创建
static修饰属性的应用场景: - 某些特定的数据想要在内存中共享,只有一块 --> 这种情况下就可以用static修饰的属性
属性:
静态属性(类变量)
实例属性(静态变量)
static修饰方法
public class Test {
int id;
static int sid;
public void a() {
System.out.println(id);
System.out.println(sid);
}
// static 和 public 都是修饰符,并列的没有先后顺序
static public void b(){
//a(); 在静态方法中不能访问非静态方法
//System.out.println(id); 在静态方法中不能访问非静态属性
//System.out.println(this.id); 在静态方法中不能使用this关键字
System.out.println(sid);
}
public static void main(String[] args) {
// 非静态方法可以用对象名.方法名去调用
Test t = new Test();
t.a();
// 静态方法可以用 类名.方法名 或 对象名.方法名 进行调用
Test.b();
t.b();
// 在同一个类中可以直接调用
b();
}
}
类的组成
类的组成:属性,方法,构造器,代码块,内部类
代码块分类:普通块,构造块,静态块,同步块
代码:
public class Test {
// 属性
int a;
static int sa;
// 方法
public void a() {
}
public static void b() {
}
// 构造器
public Test() {
this.a = a;
}
// 代码块
public void c() {
System.out.println("aa");
{
// 普通块限制了局部变量的作用范围
System.out.println("-----------------");
int num = 10;
System.out.println(num);
}
// System.out.println(num);
// if(){}
// while(){}
}
// 构造块 : 在方法外写代码可在构造块中书写
{
System.out.println("---这是构造块");
}
// 静态块 : 先于对象存在 ps:在静态块中只能访问静态方法和静态属性
static {
System.out.println("这是静态块");
}
}
总结: 代码块执行顺序
最先执行静态块,只在类加载的时候执行一次 (一般用于执行一些全局性的初始化操作)
在执行构造块,(不常用)
在执行构造器,
在执行方法中的普通块
面向对象特性
- 封装:
- 属性私有化 --> 加入权限修饰符 private 。。。
- 提供public方法,提供对属性的获取及修改
- 继承:
- 提高代码复用性:父类定义的内容,子类可以直接利用,不用代码重复定义
- 便于代码的扩展
- 是多态的前提
- 一个父类可以有多个子类
- 一个子类只能有一个直接父类
- 继承具有传递性 eg: student --> Person --> Object Object是所有类的根基类
ps: 父类private修饰的内容,子类也继承了,只是因为封装的特性阻碍了直接调用。可用set get方法进行间接调用
权限修饰符
同一个类 | 同一个包 | 子类 | 所有类 | |
---|---|---|---|---|
private | * | |||
default | * | * | ||
protected | * | * | * | |
public | * | * | * | * |
重写
- 发生在子类和父类中,当子类对父类提供的方法不满意的时候,要对父类的方法进行重写
- 重写有严格的格式要求:
- 子类的方法名字和父类必须一致,参数列表(个数,类型,顺序)也要和父类一致。
- 重载和重写的区别
- 重载:在同一个类中当方法名相同,形参列表不同的时候,多个方法构成了重载
- 重写:在不同的类中,子类对父类提供的方法不满意的时候,要对父类的方法进行重写
英文 | 位置 | 修饰符 | 返回值 | 方法名 | 参数 | 抛出异常 | 方法体 | |
---|---|---|---|---|---|---|---|---|
重载 | overload | 在同一个类中 | 无关 | 无关 | 必须相同 | 必须相同 | 无关 | 不同 |
重写 | override | 子类父类中 | 父类的权限修饰符要低于子类的 | 父类的返回值类型大于子类 | 必须相同 | 必须相同 | 小于等于 | 不同 |
super
- super指的是父类的
- super可以修饰属性,可以修饰方法
在特殊情况下,当子类和父类的属性重名时,你要想使用父类的属性,就必须加上修饰符super. 只能用super.方法进行调用,这种情况下super. 就不能省略了。 - super修饰构造器
平时写的空构造器的第一行都有super() --> 作用:调用父类的空构造器,一般省略不写
(所有构造器的第一行默认情况下都有super() 但是一旦你的构造器中显示的使用super调用了父类构造器,那么这个super()就不会给你默认分配)
在构造器中,super调用父类构造器和this调用子类构造器只能存在一个,两者不能共存
子类创建对象,需要先构造父类
Object类
所有类都直接或间接的继承自Object类,Object类是所有Java类的根基类。
也就意味着所有的Java对象都拥有Object类的属性和方法
如果在类的声明中未使用extends关键字指明父类,则默认继承Object类
-
Object类的toString方法
出现问题:子类student对父类Object提供的toString方法 不友好,子类可以重写父类的toString 方法 -
Object的equals方法
总结:
equals作用: 这个方法提供了对对象内容是否相等的一个比较方式,对象的内容指的就是属性
父类Object提供的equals就是在比较==地址,没有实际意义,我们一般不会直接使用父类提供的方法,而是在子类中对这个方法进行重写
重写equals 代码
类与类之间的关系
总结:
- 面向对象的思维:找参与者,男孩类 女孩类
- 体会了什么叫方法的实参,什么叫方法的形参
具体传入的内容实参
- 类和类可以产生关系
- 将一个类作为另一个类中的方法的形参
- 将一个类作为另一个类的属性
public class Boy {
int age;
String name;
public void buy(){
System.out.println("make friends buy buy buy");
}
public Boy(int age, String name) {
this.age = age;
this.name = name;
}
}
public class Girl {
String name;
double weight;
Mother m /*= new Mother()*/;
public Girl(String name, double weight) {
this.name = name;
this.weight = weight;
}
public void love(Boy boy){ // 引用数据类型
System.out.println("my boy friend name is " + boy.name + "my boy friends age is " + boy.age);
boy.buy();
}
public void wechat(){
m.say();
}
public void add(int a ){ // 基本数据类型
System.out.println(a);
}
}
public class Mother {
public void say(){
System.out.println("say something full of love");
}
}
public class Test {
public static void main(String[] args) {
Boy ansan = new Boy(30, "安三");
Girl lili = new Girl("丽丽",100);
lili.love(ansan);
lili.m = new Mother();
lili.wechat();
}
}
多态 (多种状态)
- 多态与属性无关,多态指的是方法的多态,而不是属性的多态
案例带入: 小女孩和动物玩
public class Animal {
public void shout(){
System.out.println("i am animal i can shout");
}
}
public class Cat extends Animal {
// 喊叫方法
public void shout(){
System.out.println("i am a cat miao~ miao~ ");
}
public void scratch(){
System.out.println("i am a cat i can scratch people");
}
}
public class Dog extends Animal {
public void shoot(){
System.out.println("i am a doy wang wang ~");
}
public void guard(){
System.out.println(" i am a doy i can protect someone");
}
}
public class Girl {
// play with cat
/*public void play(Cat cat){
cat.shout();
}
public void play(Dog dog){
dog.shoot();
}*/
public void play(Animal animal){
animal.shout();
}
}
public class Test {
public static void main(String[] args) {
Girl girl = new Girl();
//Cat cat = new Cat();
// 小女孩与猫玩耍
//girl.play(cat);
//Dog dog = new Dog();
//girl.play(dog);
Animal an = new Animal();
Animal an1 = new Cat();
Animal an2 = new Dog();
girl.play(an);
girl.play(an1);
girl.play(an2);
}
}
总结:
- 现有父类,再有子类 --> 继承 先有子类,再有父类 --> 泛化
- 什么是多态?
- 多态就是多种状态:同一个行为,不同的子类表现出来不同的形态
- 态指的就是同一个方法调用,由于对象不同,会产生不同的行为
- 多态的好处: 提高代码的扩展性,符合面向对象的设计原则:开闭原则
开闭原则指的是:扩展是开放的,修改是关闭的(修改代码)
(多态可以提高扩展性,但是扩展性没有达到最好,以后会学习到反射) - 多态的要素:
- 继承:Cat extends Animal , Dog extends Animal
- 重写:子类对父类的方法shout()重写
- 父类引用指向子类对象 Animal an2 = new Dog();
Animal an2 = new Dog();
=左侧 : 编译期的类型
=右侧: 运行期的类型
Animal an = new Dog();
g.play(an);
上面的代码,也是多态的一种常见场合:父类当方法的形参,然后传入的是具体子类的对象,
然后调用同一个方法,根据传入的子类的不同展现出来的效果也不同,构成了多态
向上转型,向下转型
简单工厂模式
多态的另一种应用:
不仅可以使用父类做方法的形参,也可以使用父类做方法的返回值类型,真实返回的对象可以是该类的任意一个子类对象。
简单工厂模式的实现,它是解决大量对象创建问题的一个解决方案。将创建和使用分开,工厂负责创建,使用者直接调用即可。
简单工厂模式的基本要求是:
1.定义一个static方法,通过类名直接调用
2.返回值类型是父类类型,返回的可以是其任意子类类型
3.传入一个字符串类型的参数,工厂根据参数创建对应的子类产品
public class Test {
public static void main(String[] args) {
Girl girl = new Girl();
/*Animal an2 = new Dog();*/
Animal dog = PetStore.getAnimal("狗");
girl.play(dog);
}
}
public class PetStore { // 宠物店
// 方法:提供动物
public static Animal getAnimal(String petName){ // 多态的应用场合:父类当作方法的返回值
Animal an = null ;
if("猫".equals(petName)){ // petName.equals("猫") --> 这样写容易发生空指针异常
an = new Cat();
}
if("狗".equals(petName)){
an = new Dog();
}
return an;
}
}
final 修饰符
- final修饰变量
public class Test {
public static void main(String[] args) {
// 第一种情况
// final 修饰变量,变量的值不可以改变,这个变量也变成了一个字符常量 , 约定俗称的规定:名字大写
final int A = 10; // final修饰基本数据类型
//A = 20; 报错
// 第二种情况
// final 修饰引用类型,地址值不能改变,
final Dog dog = new Dog();
//dog = new Dog(); 报错
// dog对象的属性,依然可以改变
dog.age = 10;
dog.weight = 13.7;
// 第三种情况
final Dog d2 = new Dog();
a(d2);
// 第四种情况
b(d2);
}
public static void a (Dog d){
d = new Dog();
}
public static void b(final Dog d){ // d 被final修饰 地址不可改变
// d = new Dog(); 报错
}
}
- final修饰方法
final 修饰的方法,子类不能进行重写
final 修饰类,代表没有子类,该类不能被继承:
一旦一个类被final修饰,那么里面的方法也没必要用final修饰了(final可省略不写)
案例: JDK提供的Math类.
1.Math类没有子类,不能被其他类继承
2.Math类中的属性全部被final修饰,方法也是被final修饰的,只是省略不写了
原因:子类没有必要进行重写
4.外界不可以创建对象
Math m = new Math();
5.发现Math类中的所有的属性,方法都被static修饰,那么不用创建对象去调用,而是可以利用类名直接调用
抽象类 抽象方法
抽象类和抽象方法的关系:
1.抽象类中可以定义 0 ~ n 个抽象方法
抽象类作用:
1.在抽象类中定义抽象方法,为子类提供一个通用的模板,子类可以在模板的基础上进行开发,先重写父类的抽象方法,然后可以扩展子类自己的内容,抽象类设计避免了子类设计的随意性,通过抽象类,子类的设计变得更加严格,进行某些程度上的限制。
// 4. 一个类中,如果有方法是抽象方法,那么这个类也要变成一个抽象类
// 5. 一个抽象类中可以有 0~n 个抽象方法
public abstract class Person {
// 1.在一个类中,会有一类方法,子类对这个方法非常满意,无需重写,直接使用
public void eat(){
System.out.println("一顿不吃饿得慌");
}
// 2. 在一个类中,会有一类方法,子类对这个方法永远不满意,会对这个方法进行重写
// 3. 一个方法的方法体去掉,被abstract修饰,这个方法就变成了一个抽象方法
public abstract void say();
}
// 6.抽象类可以被其他类继承
// 7. 一个类继承抽象类,那么这个类可以变成抽象类
// 8. 一般子类不会加abstract修饰,一般会让子类重写父类中的抽象方法
// 9. 子类继承抽象类,就必须重写全部的抽象方法
// 10. 子类如果没有重写父类全部的抽象方法,那么子类也可以变成一个抽象类
class Student extends Person{
@Override
public void say() {
System.out.println("xxxxx");
}
}
class Demo{
public static void main(String[] args) {
// 11. 创建抽象类的对象 --》 抽象类不可以创建对象
//Person p = new Person();
// 12. 创建子类对象
Student s = new Student();
s.say();
s.eat();
// 13. 多态写法:父类引用指向子类对象
Person p = new Student();
}
}
面试题
1.抽象类不能创建对象,那么抽象类中是否有构造器?
抽象类中一定有构造器。构造器的作用,给子类初始化对象的时候要先super调用父类构造器 。
2.抽象类是否可以被final修饰
不能被final修饰,因为抽象类设计的初衷就是给子类继承用的。要是被final修饰了这个抽象类,就不存在继承,没有子类。
接口
/**
* 1.类是类,接口是接口,他们是同一个层次的概念
* 2.接口中没有构造器
* 3.接口如何声明: interface
* 4.在jdk1.8之前,接口中只有两部分内容
* (1) 常量; 固定修饰符: public static final
* (2) 抽象方法: 固定修饰符 public abstract
*/
public interface TestInterface01 {
// 常量
public static final int NUM = 10;
// 抽象方法
public abstract void a();
public abstract void b(int num);
public abstract void c(String name);
}
interface TestInterface02{
void a1();
}
/*
5. 和接口的关系是什么? 实现关系 类实现接口
6. 一旦实现一个接口,那么实现类要重写接口中的全部抽象方法
7. 如果没有全部重写抽象方法,那么这个类可以变成一个抽象类
8. java只有单继承,java还有多实现
一个类继承其他类,只能直接继承一个父类
但是实现类实现接口的话,可以实现多个接口
9. 写法: 先继承,在实现
*/
class Student extends Person implements TestInterface01,TestInterface02{
@Override
public void a() {
}
@Override
public void b(int num) {
}
@Override
public void c(String name) {
}
@Override
public void a1() {
}
}
class Test{
public static void main(String[] args) {
// 10. 接口不能创建对象
//TestInterface02 in2 = new TestInterface02()
TestInterface02 t = new Student(); // 多态的应用 接口指向实现类
// 11.接口中常量如何访问
System.out.println(TestInterface01.NUM);
System.out.println(Student.NUM);
Student student = new Student();
System.out.println(student.NUM);
TestInterface01 t1 = new Student();
System.out.println(t1.NUM);
}
}
接口的作用是什么?
定义规则: 只是跟抽象类不同地方在于哪?它是接口不是类
接口定义好规则之后,实现类负责实现即可
继承与实现的应用案例
继承: 子类对父类的继承
实现: 实现类对接口的实现
案例:
手机 是不是 照相机
继承: 手机 extends 照相机 ”is -a “ 的关系,手机是一个照相机 so 利用继承的写法不好
实现: 手机 implements 拍照功能 "has - a "的关系,手机具备照相的能力
案例:
飞机 小鸟 风筝
定义一个接口 : Flyable
多态的应用场合
1.父类当作方法的形参,传入具体的子类的对象
2.父类当作方法的返回值,返回值是具体的子类对象
3.接口当作方法的形参,传入具体的实现类的对象
4.接口当作方法的返回值,返回的是具体的实现类的对象
jdk1.8之后,新增非抽象方法
1.被public default修饰的非抽象方法:
注意1:default修饰符必须要添加,否则出错
注意2:实现类中要是想重写接口中的非抽象方法,那么default修饰符必须不能加,否则出错
2.静态方法
注意1: static 不可以省略不写
注意2: static 修饰的方法不可以被重写
public interface TestInterface2 {
// 常量
public static final int NUM=0;
// 抽象方法
public abstract void a();
// public default 非抽象方法
public default void b(){
System.out.println("-----TestInterface2 ---- b");
}
// 静态方法
public static void c(){
System.out.println("-----TestInterface2 ----- static ");
}
}
class Demo implements TestInterface2{
@Override
public void a() {
System.out.println("重写了a方法");
}
public static void c(){
System.out.println("Demo中的静态方法");
}
}
class A {
public static void main(String[] args) {
Demo demo = new Demo();
demo.c();
TestInterface2.c();
}
}
疑问? 为什么jdk1.8之后要在接口中加入非抽象方法
如果接口中只能定义抽象方法的话,那么我们要是修改接口中的内容,那么对实现类的影响太大了,所有实现类都会受影响,所以1.8之后在接口中加入了非抽象方法,避免对实现类的影响
public interface TestInterface {
void a();
void b();
public default void c(){
}
}
class Test01 implements TestInterface{
@Override
public void a() {
}
@Override
public void b() {
}
}
class Test02 implements TestInterface{
@Override
public void a() {
}
@Override
public void b() {
}
}
内部类
成员内部类
/**
* 1. 类的组成: 属性 方法 构造器 代码块 (普通块 静态块 构造块 同步块) 内部类
* 2. 一个类TestOuter的内部的类SubTest叫内部类 外部类:TestOuter
* 3. 内部类 : 成员内部类(静态的 非静态的) 和 局部内部类 (位置:方法内,块内,构造器内)
* 4. 成员内部类:
* 里面属性,方法,构造器等
* 修饰符: private default protect public final abstract
*/
public class TestOuter {
// 非静态的成员内部类
class D {
int age = 20;
String name;
public void method(){
// 5. 内部类可以访问外部类的内容
/*System.out.println(age);
a();*/
int age = 30;
// 8. 内部类和外部类属性重名的时候如何进行调用
System.out.println(age);// 30
System.out.println(this.age);// 20
System.out.println(TestOuter.this.age);// 10
}
}
// 静态成员内部类
static class E {
public void method(){
// 6. 静态内部类中只能访问外部类中static修饰的内容
//System.out.println(age);
//a();
}
}
// 属性
int age = 10 ;
// 方法
public void a() {
System.out.println("这是a方法");
{
System.out.println("这是一个普通块");
class B {
}
}
class A {
}
// 7. 外部类想要访问内部类的东西,需要创建内部类的对象,然后进行调用
D d = new D();
d.method();
}
static {
System.out.println("这是静态块");
}
{
System.out.println("这是构造块");
}
// 构造器
public TestOuter() {
class C {
}
}
}
class Demo {
public static void main(String[] args) {
// 8.创建外部类的对象
TestOuter testOuter = new TestOuter();
testOuter.a();
// 9.创建内部类的对象
// 静态的成员内部类创建对象
TestOuter.E e = new TestOuter.E();
// 非静态成员内部类创建对象
// 错误 new TestOuter().D
TestOuter t = new TestOuter();
TestOuter.D d = t.new D();
}
}
局部内部类
public class TestOuter {
public void method() {
//1. 在局部内部类中访问到的变量,必须是被final修饰的
final int num = 10;
class A {
public void a() {
// num = 20;
System.out.println(num);
}
}
}
// 如果类B 在整个项目中只使用一次,那么就没有必要单独创建一个B类。使用内部类就可以了
public Comparable method2() {
class B implements Comparable {
@Override
public int compareTo(Object o) {
return 0;
}
}
return new B();
}
public Comparable method3() {
// 匿名内部类
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
public void teat(){
Comparable com = new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
System.out.println(com.compareTo("abc"));
}
}