面向对象是在面向过程之后出现的。当事件比较简单的时候,利用面向过程,注重的是事件的具体的步骤/过程,注重的是过程中的具体的行为,以函数为最小单位,考虑怎么做。
而面向对象注重找“参与者”,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做,然后通过这个对象来使用这个对象所具有的方法属性等(比如:人(类或对象)有眼睛鼻子(属性),也可以吃饭(方法).所以我们说面向对象有一句通俗的话叫做万物皆对象)。
下面我们就来详细介绍一下面向对象的概念和使用。
类的定义和代码结构
注意:第一次使用类的时候,会进行类的加载,初始化创建对象的时候,对象的属性没有给赋值,会有默认的初始化的值。而第二次使用这个类就不会进行类加载了
完整代码结构:
//类的定义
[修饰符] class 类名{
//属性的定义
[修饰符] 属性类型 属性名 = [默认值] ;
//方法的定义
[修饰符] 方法返回值类型 方法名(形参列表) {
// n条语句
}
}
对象的定义和代码结构
new关键字来实现。完整代码结构是:
[修饰符] 类名 类对象名 = new 类名(实参列表);
使用类和对象的实例
1 创建一个类,抽象出人这个实体。
2 用这个类来构造一个对象,并且使用这个对象的方法。
/**
* 创建类:人类
*/
public class Person {
//名词---》属性---》成员变量---》放在类中方法外(注意:我们只把有需要的内容写到代码里,不相关的东西不要放在代码中)
int age ;//年龄
String name;//姓名
double height;//身高
double weight;//体重
//动词---》方法
//吃饭
public void eat(){
int num = 10;//局部变量:放在方法中
System.out.println("我喜欢吃饭");
}
//睡觉:
public void sleep(String address){
System.out.println("我在"+address+"睡觉");
}
//自我介绍:
public String introduce(){
return "我的名字是:"+name+",我的年龄是:"+age+",我的身高是:"+height+",我的体重是:"+weight;
}
}
/**
* 测试类,使用刚才创建的Person类
*/
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建一个人类的具体的对象/实例:
//创建一个对象,对象的名字叫:zhangsan
//Person 属于 引用数据类型
//第一次加载类的时候,会进行类的加载,初始化创建对象的时候,对象的属性没有给赋值,有默认的初始化的值。
Person zs= new Person();
zs.name = "张三";
zs.age = 19;
zs.height = 180.4;
zs.weight = 170.4;
//再创建一个对象:名字叫:lisi
//再次创建类的时候,就不会进行类的加载了,类的加载只在第一次需要的时候加载一次
Person ls = new Person();
ls.name = "李四";
ls.age = 18;
ls.height = 170.6;
ls.weight = 160.5;
//对属性值进行读取:
System.out.println(zs.name); // 张三
System.out.println(ls.age); // 18
//对方法进行操作:
//不同的对象,属性有自己的特有的值,但是方法都是调用类中通用的方法。
//属性:各个对象的属性是独立的,
//方法:各个对象的方法是共享的。
zs.eat(); // 我喜欢吃饭
ls.eat(); // 我喜欢吃饭
zs.sleep("教室"); // 我在教室睡觉
/*String str = zs.introduce();
System.out.println(str);*/
System.out.println(zs.introduce()); // 我的名字是:张三,我的年龄是:19,我的身高是:180.4,我的体重是:170.4
}
}
区别1:代码中位置不同
局部变量:方法中定义的变量
成员变量:类中(方法外)定义的变量
区别2:代码的作用范围不同
局部变量:当前的方法内
成员变量:作用于当前类的所有方法中
区别3:变量是否有默认值
局部变量:无
成员变量:有,↓为成员变量的默认值

区别4:变量是否要初始化
局部变量:一定需要,不然使用的时候会报错
成员变量:不需要,可以使用的时候对它进行赋值
区别5:内存中的位置不同
局部变量:栈内存
成员变量:堆内存
区别6:作用时间不同
局部变量:当前方法从开始到执行结束
成员变量:当前类的对象从创建到销毁的整个过程
在创建对象的过程中,是通过类内的一个特殊方法(构造器)来实现创建的。
具体过程见例子:
class Test {
public static void main(String[] args) {
//创建一个Person类的具体的对象/实例/实体:
/*
创建对象的过程:
1.第一次遇到Person的时候,进行类的加载(只加载一次)
2.创建对象,为这个对象在堆中开辟空间
3.为对象进行属性的初始化动作
new关键字实际上是在调用一个方法,这个方法叫构造方法(构造器)
调用构造器的时候,如果你的类中没有写构造器,那么系统会默认给你分配一个构造器,只是我们看不到罢了。
可以自己显式 的将构造器编写出来:
构造器的格式:
[修饰符] 构造器的名字(){
}
构造器和方法的区别:
1.没有方法的返回值类型
2.方法体内部不能有return语句
3.构造器的名字很特殊,必须跟类名一样
构造器的作用:不是为了创建对象,因为在调用构造器之前,这个对象就已经创建好了,并且属性有默认的初始化的值。
调用构造器的目的:给属性进行赋值操作的。
注意:我们一般不会在空构造器中进行初始化操作,因为那样的话每个对象的属性就一样了。
实际上,我们只要保证空构造器的存在就可以了,里面的东西不用写
*/
Person p = new Person();
System.out.println(p.age);
System.out.println(p.name);
System.out.println(p.height);
Person p2 = new Person();
System.out.println(p2.age);
System.out.println(p2.name);
System.out.println(p2.height);
}
}
构造器是一个特殊方法,所以也可以重载。但是当你显示的写了一个类的重载构造器之后,默认的空参构造器就需要显示写出来,不然会报错。
想要指向类对象中的属性时,使用this关键字。this代表你创建的那个对象
public class Person {
//属性:
String name;
int age;
double height;
//空构造器
public Person(){
}
public Person(String name,int age,double height){
//当形参名字和属性名字重名的时候,会出现就近原则:
//在要表示对象的属性前加上this.来修饰 ,因为this代表的就是你创建的那个对象
this.name = name;
this.age = age;
this.height = height;
}
public Person(String a,int b){
name = a;
age = b;
}
//方法:
public void eat(){
System.out.println("我喜欢吃饭");
}
}
public class Person {
int id;
int age;
String school;
public Person (int a,int b,String c){
id=a;
age=b;
school=c;
}
public static void main(String args[]){
Person p= new Person(1,20, "");
}
}
点击运行时从main函数开始的:
(1)先开辟三个空间:栈,堆,和方法区(方法区内有一个地方为常量池)。
(2)在栈中开辟一个空间main方法。
(3)首先看到new Person,在方法区中加载这个Person字节码文件(只有第一次会加载),然后在堆中开辟一个空间来创建一个Person对象(这个对象有一个地址)。这个对象也会反过来指向它的字节码文件,用来指定自己是依靠谁来创建的。
(4)创建好了对象之后,new Person(1,20,"海淀")就去执行它的构造方法。会在栈中开辟一个空间Person构造器方法。
(5)在栈实参传入构造器方法的过程中。
如果是基本数据类型,那么直接传的就是这个值本身(值传递)。
如果是引用数据类型,比如String,那么会先在常量池中开辟一个空间来放置"海淀"这个字符串,然后把地址传给c(地址传递)。
(6)构造器执行完毕后,就会在栈中销毁,然后转入main方法。
(7)Person p = 。。。 之后把这个对象的地址赋值给p。(之后就可以通过p来操作这个对象)
| 整个程序运行的内存图解 |
|---|
![]() |
this,static,代码块this :指的是当前对象
当属性名字和形式参数发生重名的时候,或者属性名字和局部变量的名字重名的时候,都会发生就近原则,所以我们如果要直接使用属性的话,前面就要用this修饰这个变量。(如果没有重名就无所谓使用了)
| this关键字使用的图解 |
|---|
![]() |
static关键字特点
代码实例:
/**
* static的使用
*/
public class Demo1 {
int id;
static int sid;
public void a(){
System.out.println(id);
System.out.println(sid);
System.out.println("------a");
}
// 1.static和public都是修饰符,并列的没有先后顺序,先写谁后写谁都行
static public void b(){
//System.out.println(id); // 2.在静态方法中不能访问非静态的属性
//a(); // 3.在静态方法中不能访问非静态的方法
//System.out.println(this.id); // 4.在静态方法中不能使用this关键字
System.out.println(sid);
System.out.println("------b");
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//5.非静态的方法可以用对象名.方法名去调用
Demo1 d = new Demo1();
d.a();
//6.静态的方法可以用 对象名.方法名去调用 也可以用 类名.方法名 (推荐)
Demo1.b();
d.b();
}
}
代码块的特点
static{ }结构包裹的内容代码实例:
/**
* 代码块的执行顺序
*/
public class Demo1 {
//属性
int a;
static int sa;
//方法
public void a(){
System.out.println("我是普通方法a");
{
//普通块限制了局部变量的作用范围
System.out.println("我是普通方法里面的普通块");
}
}
public static void b(){
System.out.println("我是静态方法b");
}
//构造块
{
System.out.println("我是类下的构造块,{}");
}
//静态块
static{
System.out.println("我是类下的静态块,static{}");
//在静态块中只能方法:静态属性,静态方法
System.out.println("我是类下的静态块,只能调用静态属性"+sa);
}
//构造器
public Demo1(){
System.out.println("我是类下的空构造器");
}
public Demo1(int a){
this.a = a;
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Demo1 t = new Demo1();
t.a();
System.out.println("------------------第二次---------------");
Demo1 t2 = new Demo1();
t2.a();
}
}
包,import包的定义和特点
. 隔开com.jd com.uestccom.jd.login com.jd.registernul con com1至com9…import **.**.**; 例如:import java.util.Date;java.lang包下的类,可以直接使用无需导包。System.out.println(Math.random());
alt+enterimport 。。。.*代表所有import java.util.*
import com.jayden.* //只代表jayden这一层的类,如果里面有还有包,那么这个包并没有加入
import com.jayden.sub.D //因为如果jayden下如果分别有两个包,两个包都有sub方法就会导致歧义
//静态导入:
import static java.lang.Math.*;
//导入:java.lang下的Math类中的所有静态的内容
public class Test {
public static void main(String[] args) {
System.out.println(random());
System.out.println(PI);
System.out.println(round(5.6));
}
//在静态导入后,同一个类中有相同的方法的时候,会优先走自己定义的方法。
public static int round(double a){
return 1000;
}
}
面向对象的三大特性是指:封装,继承,多态。
封装的定义:封装就是具体的内部不给你看,只提供有限的接口供你使用的一种设计思想。
封装的特点
private权限修饰符来限制
一般属性用private修饰,方法用public修饰
代码实例:
/**
* 用private封装属性
*/
public class Girl {
//属性: private:私有化,外界不可以直接访问
private int age;
//读取年龄: public:外界可以直接访问
public int getAge(){
return age;
}
//设置年龄:
public void setAge(int age){
if(age >= 30 ){
this.age = 18;
}else{
this.age = age;
}
}
}
public class Demo1 {
public static void main(String[] args) {
//创建一个Girl类的对象:
Girl g = new Girl();
/*g.age = 33;
System.out.println(g.age);*/
//设置年龄:
g.setAge(31);
//读取年龄:
System.out.println(g.getAge());
}
}
上面的代码,对于属性age来说,我加了修饰符private,这样外界对它的访问就受到了限制,现在我还想加上其他的限制条件,但是在属性本身上没有办法再加了,所以我们通过定义方法来进行限制条件的添加。
以属性为案例:
进行封装:
(1)将属性私有化,被private修饰(加入权限修饰符)
一旦加入了权限修饰符,其他人就不可以随意的获取这个属性
(2)提供public修饰的方法让别人来访问/使用
(3)即使外界可以通过方法来访问属性了,但是也不能随意访问,因为咱们在方法中可以加入限制条件。
继承的定义:继承就是对类的进一步抽象,多个类可以能有相同的属性和方法,这个时候就可以对这几个类抽取出所谓的父类。
EG:
**学生类**:Student:
属性:姓名,年龄,身高,学生编号
方法:吃饭,睡觉,喊叫,学习
**教师类**:Teacher:
属性:姓名,年龄,身高,教师编号
方法:吃饭,睡觉,喊叫,教学
共同的东西:**人类**:Person
属性:姓名,年龄,身高
方法:吃饭,睡觉,喊叫
学生和老师都是人,如果还有100种职业的话,比如都有属性姓名属性和吃饭的方法,那么每一个类中都要重新写一遍就太麻烦了,所以定义它们的父类Person,只要用字段entend关键字继承Person,就会获得Person的所有共有属性和方法。
继承的特点
//报错,只能有一个直接父类
public class Student extends Person,Boss{
}
内存分析:在new一个子类对象的时候,会先加载这个子类所有父类的字节码,然后再加载这个子类的字节码。
注意:父类private修饰的内容,子类实际上也继承,只是因为封装的特性阻碍了直接调用,但是提供了间接调用的方式,可以间接调用。
方法重写的定义和特点:
方法名字和父类必须一致,参数列表(个数,类型,顺序)也要和父类一致。代码实例:
/**
* 继承中的重写
*/
class Person {
public void eat(){
System.out.println("吃食物");
}
}
class Student extends Person {
@Override
public void eat(){
System.out.println("我喜欢吃小龙虾喝啤酒。。");
}
}
public class Demo1 {
public static void main(String[] args) {
//创建一个Student类的对象:
Student s = new Student();
//打印的是子类Student中重写的的eat方法,而不是父类Person的
s.eat(); // 我喜欢吃小龙虾喝啤酒。。
}
}
重载和重写的区别:重载(Overload)是在同一个类中方法名相同,
单参数列表不同的多个方法构成的结构。而重写(Override)是在不同类中,方法名和参数名都必须相同(注意:重写中父类的修饰符权限一定要低于子类的,返回值类型要大于子类的)
super关键字的定义和特点
super.属性 super.方法 的方式,显示的去调用父类提供的属性,方法。在通常情况下,super.可以省略不写。super.,只能通过super.属性来调用。public Student(int age,String name,double score){
super(age,name);
this(score);//调用子类的public Student(double score)构造器报错
}
多态的定义:多态就是对同一个方法的不同实现。(继承是多态的前提)
多态的特点
代码实例:多态的应用
class Animal {//父类:动物:
public void shout(){
System.out.println("我是小动物,我可以叫。。。");
}
}
class Cat extends Animal{//子类1:猫
//喊叫方法:
public void shout(){
System.out.println("我是小猫,可以喵喵叫");
}
}
class Dog extends Animal{//子类2:狗
int age;
int weight;
//喊叫:
public void shout(){
System.out.println("我是小狗,我可以汪汪叫");
}
public void eat(){
System.out.println("我是小狗,我爱吃东西。。");
}
}
class Girl { //多态应用1:原本要写两个方法现在只需要写一个
//跟猫玩耍:
/*public void play(Cat cat){
cat.shout();
}*/
//跟狗玩耍:
/*public void play(Dog dog){
dog.shout();
}*/
//跟小动物玩耍:
public void play(Animal an){
an.shout();
}
}
public class Demo1 {
public static void main(String[] args) {
//eg1:转化为父类根据对象使用对应的方法
Cat cat = new Cat();
Dog dog = new Dog();
//Animal an = ?
Animal animal = dog;//转型:向上转型(Dog转成Animal)
//多态应用2:下面代码就不用变了
animal.shout();
//注意an只能用Animal中存在的方法,虽然它的对象是Dog,但在编译期的时候因为Animal没有eat属性所以会报错
// an.eat();
//eg2:转化为子类后可以用子类的方法
Dog dog2 = (Dog)animal ;//转型:向下转型(Animal转为Dog)
dog2.eat();
}
}
多态的应用中,不仅可以使用父类做方法的形参,还可以使用父类做方法的返回值类型,真实返回的对象可以是该类的任意一个子类对象。
这种方法是简单工厂模式的实现,它是解决大量对象创建问题的一个解决方案。将创建和使用分开,工厂负责创建,使用者直接调用即可。简单工厂模式的基本要求是
(1)定义一个static方法,通过类名直接调用
(2)返回值类型是父类类型,返回的可以是其任意子类类型
(3)传入一个字符串类型的参数,工厂根据参数创建对应的子类产品
代码实例:
/**
* 多态应用2,返回值抽象成父类
*/
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;
}
}
class Demo2 {
public static void main(String[] args) {
Girl g = new Girl();
//Cat c = new Cat();
//Dog d = new Dog();
Animal an = PetStore.getAnimal("狗");
g.play(an); // 我是小狗,我可以汪汪叫
}
}