一、面向对象
1什么是面向对象
- Java的编程语言是面向对象的,采用这种语言进行编程成为面向对象编程(object-oriented-programming,OOP)
- 面向对象编程的本质:以类的方式组织代码,以对象的组织(封装)数据。
- 面向对象的三大基本特征:封装,继承,多态。
- 从认识角度,先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象。从代码角度,现有类后有对象。类是对象的模板。
2类与对象的关系
-
类是抽象的数据类型,对某一类事物整体描述和定义,但不能代表某一个具体的事物。
-
对象是抽象概念的具体实例。
Student xiaoming = new Student();
-
对象小明就是Student类的一个实例。Student类只是给对象xiaoming的创建提供的一个参考模板,是抽象出来的。
3对象和引用的关系
-
引用“指向”对象
-
使用类类型、数组类型、接口类型声明出的变量,都可以指向对象,这种变量就是引用类型变量,简称引 用。 在程序中,创建出对象后,直接使用并不方便,所以一般会用一个引用类型的变量去接收这个对象,这个就是 所说的引用指向对象。
Student student= new Student("张三");
-
student指向了“小明”这个对象
二、创建对象和初始化
-
使用new关键字创建对象,用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
Student s = new Student();
三、构造器
- 构造器,也被称为构造方法,是在进行创建对象的时候必须要调用的。
- 必须和类的名字相同。
- 必须没有返回类型,也不能用void。
- 构造器的作用
- 使用new创建对象的时候必须使用类的构造方法。
- 构造器可以给对象中的属性初始化赋初值。
- 默认构造器
- 编写类的时候,没有写构造器,编译之后也会自动添加一个无参构造器,这个构造器就是默认构造器。
- 注意点
- 有参构造器,在创建对象的时候可以给属性赋值(构造器重载)
- 定义有参构造之后,如果想使用无参构造,需要显式的定义一个无参构造。
- Alt + Insert 创建构造器的快捷键。
- this关键字:表示当前类中的。
四、内存分析
- 栈(stack)
- 每个线程私有,不能实现线程间的共享!
- 存放基本变量类型(包含这个基本类型的具体数值)
- 引用对象的变量(存放这个引用在堆里面的具体地址)
- 堆(heap)
- 存放new的对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
- 方法区(也是堆)
- 被所有线程共享
- 存放程序中永远是不变或唯一的内容(类代码信息,静态变量,字符串常量)
五、封装
1.封装的步骤
- 属性私有,get/set,
- 使用private修饰需要封装的成员变量
- 提供一个公开的方法设置或者访问私有的属性
- 访问通过get()方法:getName()
- 设置通过set()方法:setName()
//提供一些可以操作这个属性的方法
//提供一些public 的 get set方法
//get得到这个数据
public String getName(){
return name;
}
//set给这个属性设置值
public void setName(String name){
this.name = name;
}
//Alt + Insert 快捷键
2.封装的作用和意义
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一用户的调用接口
- 系统可维护增加了
- 便于调用者调用
3.方法重载
- 类中有多个方法,有着相同的方法名,但是方法的参数各不相同,这种情况被称为方法重载
public class Test{
public void test(String str){
}
public void test(int a){
}
}
方法重载满足条件:
1. 方法名不同
2. 参数列表必须不同(参数的类型、个数、顺序的不同)
3. 方法的返回值可以不同,也可以相同。
六、继承
1.继承
-
继承是类和类之间的一种关系,继承关系的俩个类,一个是子类(派生类),一个是父类(基类)。子类继承父类用关键字extends表示。
public class Student extends Person {}
-
子类和父类之间,从意义上讲具有“is a”的关系。
student is a person,dog is a animal ,
-
类和类之间的继承是单继承。一个子类只能直接继承一个父类,一个父类可以被多个子类继承。
-
子类继承父类的方法和属性后,子类中能不能直接使用父类的这些方法和属性,是和这些属性和方法的修饰符相关的。
2.Object类
- java中每一个类都是直接或者间接调用的继承了Object类,所以每一对象都和Object类有“is a”的关系,任何一个类最上层的父类都是Object(除了自己本身)任何一个对象都可以调用从Object继承过来的方法。
3.Super关键字
- 子类继承父类后,在子类中可以用this来表示访问或调用子类中的属性和方法,用super表示访问或调用父类中的属性和方法。
- 父类中的构造器不能被子类继承,但是子类的构造器中,会隐式的调用父类中的无参构造器(默认使用 super关键字)。
- super注意点
- super调用父类的构造方法,必须在构造方法的第一个。
- super必须只能出现在子类的方法或构造方法中。
- super和this不能同时调用构造方法!
- super 和 this 区别
- 代表的对象不同
this: 本身调用者这个对象
super: 代表父类对象的引用 - 使用前提
this:没有继承也可以使用
super:只能在继承条件下才可以使用 - 调用构造方法
this();调用本类的构造
super();调用父类的构造
- 代表的对象不同
4.方法重写
-
方法重写需要有继承关系,只存在于子类和父类之间,子类重写父类的方法。
- 方法名字必须相同,参数列表必须相同,方法体不同。
- 修饰符:范围可以被扩大,但不能被缩小。 public>protected>default>private
- 抛出的异常:范围可以被缩小,但不能被扩大。 ClassNotFoundException–>Exception(大)
- 返回类型可以相同,也可以不同,如果不同的话,子类重写后的方法返回类型必须是父类方法返回类型的子类型。
-
静态方法不能重写
- 父类的静态方法不能被子类重写为非静态方法
- 父类的非静态方法不能被子类重写为静态方法
A类继承B类 A和B中都一个相同的静态方法test
B a = new A();
a.test();//调用到的是B类中的静态方法test
A a = new A();
a.test();//调用到的是A类中的静态方法test
可以看出静态方法的调用只和左边变量声明的类型相关,和非静态方法重写之后的效果不同
-
为什么要重写?
父类的功能,子类不一定需要,或者不一定满足子类的功能需求!
-
快捷键 Alt + Insert Override
七、多态
1.多态
-
多态性是OOP中的一个重要特性,程序的最终状态只有在执行过程中才被决定而非在编译期间就决定了。
-
一个对象的实际类型是确定的,可以指向对象的引用的类型就不确定了,(父类的引用指向子类)
子类能调用自己的方法或者继承的父类的方法,父类可以指向子类,但不能调用子类独有的方法。
子类调用继承的父类的方法,如果子类重写了该方法,则调用重写之后的方法。
public static void main(String[] args) {
//一个对象的实际类型是确定的
//new Student();
//new Person():
//Person 有 ran(),Student 有 eat()和重写的run()
//可以指向对象的引用的类型就不确定了,父类的引用指向子类
Student s1 = new Student();//Student(子类)能调用的方法都是自己的或者继承父类的
Person s2 = new Student();//Person(父类)可以指向子类,但不能调用子类独有的方法
Object s3 = new Student();
s2.run();//子类重写了父类的方法,执行子类的方法
s1.run();
//对象能执行哪些方法,主要看对象左边的类型,和右边关系不大!
s1.eat();
//s2.eat();父类不能调用子类独有的方法
}
2.重写、重载和多态的关系
-
重载是编译时多态
调用重载的方法,在编译期间就要确定调用的方法是谁,如果不能确定则编译报错
-
重写是运行时多态
调用重写的方法,在运行期间才能确定这个方法到底是哪个对象中的。这个取决于调用方法的引用,在运行期间所指向的对象是谁,这个引用指向哪个对象那么调用的就是哪个对象中的方法。(java中的方法调用, 是运行时动态和对象绑定的)
3.多态注意事项
-
多态是方法的多态,属性没有多态。
-
调用方法有时候需要进行类型转换,父类和子类要有联系, 如果没有联系,类型转换异常,报错: ClassCastException!
4.多态存在条件
-
继承关系。
-
方法需要重写。
无法重写的情况:
1.static方法,属于类,他是不属于实例
2.final 常量
3.private方法 私有的 -
父类引用指向子类对象。
5.instanceof 和 类型转换
-
instanceof
-
System.out.println(X instanceof Y);
-
编译是否能通过,主要是看声明变量X的类型和Y是否存在父子关系。存在,编译通过,不存在,编译不通过。
-
输出为false还是true,主要是看变量X指向的对象实际类型是不是Y类型的子类型。
-
-
public static void main(String[] args) {
//object>string
//object>person>teacher
//object>person>student
//System.out.println(X instanceof Y);//编译是否通过
Object object = new Student();
System.out.println(object instanceof Student);//t
System.out.println(object instanceof Person);//t
System.out.println(object instanceof Object);//t
System.out.println(object instanceof Teacher);//f
System.out.println(object instanceof String);//f
System.out.println("============================");
Person person = new Student();
System.out.println(person instanceof Student);//t
System.out.println(person instanceof Person);//t
System.out.println(person instanceof Object);//t
System.out.println(person instanceof Teacher);//f
//System.out.println(person instanceof String); //编译报错
System.out.println("============================");
Student student = new Student();
System.out.println(student instanceof Student);//t
System.out.println(student instanceof Person);//t
System.out.println(student instanceof Object);//t
//System.out.println(student instanceof Teacher); //编译报错
//System.out.println(student instanceof String); //编译报错
}
- 类型转换
- 为什么要类型转换
public static void main(String[] args) {
//object>person>teacher
//object>person>student
为什么类型转换
Person person = new Student();
//将这个对象强制转换为student类型,就可以使用student类型的方法了!
person.go()//编译报错,需要将变量person的类型进行强制转换为student类型
Student student = (student)person
student.go()
//或者
((Student) person).go();
}
-
类型转换基本问题
X x = (X)o
运行是否报错,主要是变量o所指向的对象实现类型,是不是X类型的子类型,如果不是则运行就会报错。
public static void main(String[] args) {
//object>person>teacher
//object>person>student
类型转换中一些问题
Object o = new Student();//编译通过 运行没问题
Person p = (Person)o;
Object o = new Student();//编译通过 运行没问题
Student s = (Student)o;
Object o = new Teacher();//编译通过,运行报错
Student s = (Student)o;
}
即: X x = (X)o
运行是否报错,主要是变量o所指向的对象实现类型,是不是X类型的子类型,如果不是则运行就会报错。
-
总结
-
父类引用指向子类的对象
-
把子类对象直接赋值给父类引用(子类转换为父类),向上转型,丢失子类本来的一些方法
Father father = new Son();
-
把指向子类对象的父类引用赋值给子类引用(父类转换为子类),向下转型,需要强制类型转换,(son)必须添加,进行强制转换
Son son = (Son)father;
-
方便方法的调用,减少重复的代码,简洁
-
八、修饰符
1.static修饰符
-
static变量
静态变量和非静态变量的区别:
-
静态变量属于类,在类的加载过程中分配内存,可以使用类名来访问(推荐),也可以使用对象来访问
-
非静态变量属于对象,在实例变量创建对象时分配内存,必须使用对象来访问
-
-
static方法
静态方法和非静态方法的区别:
-
静态方法属于类,可以使用类名来调用(推荐),也可以使用对象来调用
静态方法不可以直接访问类中的非静态变量和非静态方法,但可以直接访问类中静态变量和静态方法
父类的静态方法可以被子类继承,但是不能被子类重写为非静态方法
-
非静态方法属于对象,必须使用对象来调用
非静态方法可以可以直接访问类中静态方法和静态变量,也可以直接访问类中的非静态方法和非静态变量
父类的非静态方法不可以被子类重写为静态方法
-
-
匿名代码块和静态代码块
-
匿名代码块在创建对象的时候自动执行,在构造器执行之前,每次创建对象都会自动执行
作用:给对象的成员变量初始化赋初值(同构造器的作用)
-
静态代码块在类加载完成之后就自动执行,只执行一次
作用:给类中的静态成员变量初始化赋初值
-
public class Person {
//第二个执行 赋初值
{
System.out.println("匿名代码块");
}
//第一个执行 只执行一次
static{
System.out.println("静态代码块");
}
//第三个执行
public Person() {
System.out.println("构造方法");
}
public static void main(String[] args) {
Person p1 = new Person();
System.out.println("=============");
Person p2 = new Person();
}
静态代码块
匿名代码块
构造方法
=============
匿名代码块
构造方法
-
创建和初始化对象的过程
Student s = new Student();
Student类之前没有进行类加载
- 类加载,同时初始化类中静态的属性
- 执行静态代码块
- 分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
- 调用Student的父类构造器
- 对Student中的属性进行显示赋值(如果有的话)
- 执行匿名代码块
- 执行构造器
- 返回内存地址
-
静态导入
用import static 导入类里的静态方法
//静态导入包
import static java.lang.Math.random;
public class Test {
public static void main(String[] args) {
System.out.println(Math.random());//随机数
System.out.println(random());//随机数
}
}
2.final修饰符
- final修饰的方法可以被继承,不能被子类重写
- final修饰的属性不可变,常量
- final修饰的类不能被继承,没有子类
3.abstract修饰符
- 抽象类和抽象方法:抽象类中可以有抽象和非抽象方法,抽象方法一定在抽象类中
public abstract class Action {
//约束 子类帮我们实现
//abstract, 抽象方法,只有方法的名字,没有方法的的实现。
public abstract void doSomething();
//1.不能new这个抽象类,只能靠子类去实现,:约束
//2.抽象类中可以写普通的方法
//3.抽象方法必须在抽象类中
//抽象的抽象:约束
//存在意义:提高开发效率
}
- 抽象类不能用new来创建对象,只能用子类继承
- 抽象方法,只有方法声明,没有方法实现,用子类来实现
- 子类继承抽象类,子类就必须实现抽象类中没有实现的抽象方法,除非子类也是abstract类
九、接口
-
接口就是比抽象类还抽象的抽象类。接口是多继承
用关键词interface声明,
用关键词implements实现,
-
接口中的方法都是抽象方法(public abstract)
接口中的变量都是静态常量(public static final)
public interface UserService {
//静态常量 ~ public static final
public static final int age = 20;
//接口中的所有定义的方法都是抽象方法 ~ public abstract
public abstract void add(String name);
public abstract void delete(String name);
void update(String name);
void query(String name);
}
-
一个类可以实现多个接口(多继承),一个接口可以继承多个父接口。
接口不能被实例化,接口中没有构造方法
类实现某个接口必须重写接口中所有的抽象方法,除非这个类为抽象类
不允许创建接口的实例,但允许定义接口类型的引用变量,该引用变量引用实现了这个接口的类的实例
-
接口的作用:
达到统一访问,创建对象的时候用接口创建,【接口名】 【对象名】 = new【实现接口的类】
十、内部类
1.内部类
- 内部类就是在一个类的内部再定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。
- 实例化内部类需要先实例化外部类,通过外部类去调用内部类。
public static void main(String[] args) {
Outer outer = new Outer();
outer.out();
//通过外部类来实例化内部类
Outer.Inner inner = outer.new Inner();
inner.in();
inner.getid();
}
2.成员内部类
- 成员内部类中不能写静态属性和静态方法,
- 成员内部类可以访问外部类的所有属性(包括私有的成员变量,方法)
public class Outer {
private int id = 10;
public void out(){
System.out.println("这是外部类的方法");
}
//成员内部类,加上static 就是静态内部类
public class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
//内部类获得外部类的私有属性
public void getid(){
System.out.println(id);
}
}
//局部内部类 外部类创建的方法中的内部类
public void method(){
//局部内部类
class Inner01{
public void in(){
}
}
}
}
3.静态内部类
- 用static修饰的内部类就是静态内部类
4.局部内部类
- 外部类创建的方法中的内部类
5.匿名内部类
- 一个对象只需要使用一次,只需要new Object().method(),就可以了。不需要给这个实例保存到该类型变量中去,这是匿名对象。匿名内部类,也是只需要使用一次,不需要在类中先定义一个这样的内部类,等待在需要的时候,临时实现这个内部类。
//匿名内部类
public class Test {
public void main(String[] args) {
//Apple apple = new Apple();
//没有名字初始化类,不将实例保存到变量中
new Apple().eat();
}
}
class Apple{
public void eat(){
System.out.println("1");
}
}