JAVA 面向对象

第五章 面向对象

面向对象技术利用对现实世界中对象的抽象和对象之间相互关联及相互作用的描述来对现实世界进行模拟,并且使其映射到目标系统中。其以基本对象模型为单位,将对象内部处理细节封装在模型内部,重视对象模块间的接口联系和对象与外部环境间的联系,能层次清晰地表示对象模型。面向对象的特点主要概括为抽象性、继承性、封装性和多态性。

java 是单继承,多实现的面向对象的高级语言。OOP OOD AOP

面向对象程序设计(Object Oriented Programming)

面向对象设计(Object-Oriented Design,OOD)

面向切面编程 AOP Aspect Oriented Programming

面向对象的三大特点:封装 继承 多态

  • 抽象(封装)
  • 继承
  • 多态

5.1 类(class)与对象

类和对象的概念?

类就是一块模板,确定对象将会拥有的特征(属性)和行为(方法)

具有相同属性和方法的一组对象的集合,就是类,类是抽象的。

声明类、属性、方法、初始化块可以多个、静态块也可以多个

简单来说对类进行实例化就是对象,类通俗来讲就像是抽象的东西,比如鸟类,这就是一种类,而对象就像是麻雀,老鹰这就是对象,是具体的。

class User{
  public static void main(String [] args){
   User u=new User();//实例化对象,通过new关键字进行实例化对象
  }
}

5.1.1 类的声明

User.java类里面有User和Teacher两个外部类,还有User.Book这个内部类

package cn.yuan;
public class User {
    //成员内部类
    class Book {
    }
//外部类
class Teacher {
}

5.1.2 属性

访问修饰符

public private protected 以及default这几个访问修饰符的访问范围如下图:

访问修饰符本类同包下类子类其他实例对象
private private int id;
默认(friendly、default) int id;
protected protected int id;
public public int id;
package cn.yuan;
public class C1 {
    /*属性 成员变量*/
    public int id;//全局的
    private final int num = 18;//私有
    protected String address = "郑州";//受保护的
    String name = "张三";//default 的

}

静态属性,又叫类变量,可以通过类名直接访问。并且只分配一次内存。

package cn.yuan;
public class C2 {
    public static int a = 4;

    public static void main(String[] args) {
        System.out.println(C2.a);
    }
}

常量 final

public final int a=18;//常量不能修改只能使用

5.1.3 方法相关的概念

public(访问修饰权限)static(静态方法),当前方法不需要实例化对象,直接类名调用 void (返回值类型,当前是无返回值) 方法名+(形参变量)
{}方法体

方法的调用:

静态方法直接类名.方法名就可以调用,普通方法需要实例化对象在调用方法使用。

package cn.yuan;
public class C2 {
    public static int a = 4;

    public static int print(int i) {
        return i;
    }

    public int print2(int i) {//i就是形参
        return i;
    }

    public static void main(String[] args) {
        System.out.println(C2.print(3));
        C2 c = new C2();
        System.out.println(c.print2(7));
    }
}
运行结果:
3
7
publi class User{
     /*编写一个方法,要求返回多个值*/
    public int[] get() {
        return new int[]{1, 2, 3};
    }
     /*编写一个方法,要求形参个数不确定*/
    public int mysum(int[] ns) {
        int s = 0;
        for (int t : ns
        ) {
            s += t;
        }
        return s;
    }

    public int mys(int... n) {
        return mysum(n);
    }
}
public class Demo {
    public static void main(String[] args) {
        User u1 = new User();
        User u2 = new User();
System.out.println(u2.mysum(new int[]{}));
System.out.println(u1.mysum(new int[]{1, 2, 3}));
System.out.println(Arrays.toString(u2.get()));
System.out.println(u1.mys(1, 2, 3, 4, 5));
    }
}
运行结果:
0
6
[1, 2, 3]
15

入口方法:

public static void main(String [] args){}

一个类中只能写一个入口方法,代表当前类可以独立运行。方法的参数String[] 参数

package cn.yuan;

public class C5 {
    public static void main(String[] args) {
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}
运行结果:
1
2
3

构造方法:

方法和类名一致,没有返回值,不使用void,此方法不直接通过实例对象调用,此方法在实例化对象时new 的时候就开始执行构造方法,在构造方法的第一行代码,默认是super();

注意:如果在编写类的时候编写了构造方法,建议再编写无参构造方法,如果没有编写构造方法,默认给一个无参的构造方法。
public class Stu{
   int id;
 public Stu(){}
 public Stu(int id){
 this.id=id;
 }
}

重载方法:

在一个类,或多个继承关系中,存在方法名称一样,方法参数不一样。

构造方法的重载:

public static int sum(int x, int y) {
    return x + y;
}

public static int sum(int x, int y, int z) {
    int t = x * y * z;
    return t;
}

静态方法:

静态方法,调用执行不需要实例化,可以根据类名.方法名直接调用执行。

在方法内部可以直接调用静态方法,直接使用静态成员类变量(属性)。

package cn.yuan;

public class C2 {
    public static int a = 4;//静态成员属性

    public static int print(int i) {//静态方法
        return i;
    }

    public int print2(int i) {
        return i;
    }

    public static void main(String[] args) {
        System.out.println(C2.a);
        System.out.println(C2.print(3));
        C2 c = new C2();
        System.out.println(c.print2(7));
    }
}

静态导入:

import static java.lang.Math.random;
System.out.println(random());

5.1.4 装箱拆箱

Java中的基本数据类型不是对象型(引用类型)。但是在程序中有时需要对对象而不是基本数据类型进行操作。因此,java里提供了一种叫做包装类(wrapper),它能够把基本数据类型包装成对象类型

Java中的包装器类有两个主要的作用

1.提供一种机制,将基本值“包装”到对象中,从而使基本值能够包含在为对象而保留的操作中,或者从带对象返回值的方法中返回。注意,java5增加了自动装箱和拆箱,程序员过去需手工执行的许多包装操作,现在可以由java自动处理了。

2.为基本值提供分类功能。这些功能大多数于各种转换有关:在基本值和String 对象间相互转换,在基本值和String 对象之间按不同基数转换,如二进制、八进制和十六进制等。

基本数据类型及包装类型的对应关系

boolean Boolean

byte Byte char Character double Double float Float int Integer long Long short Short

基本类型包装器类型(对象类型) valueOf() .parse()
booleanBoolean
charCharacter
intInteger
byteByte
shortShort
longLong
floatFloat
doubleDouble

自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动地转换成对应的对象。自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。

如果你在Java1.5下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合(Collections)中放入原始类型值,因为集合只接收对象。通常这种情况下你的做法是,将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。使用Integer,Double,Boolean等这些类我们可以将原始类型值转换成对应的对象,但是从某些程度可能使得代码不是那么简洁精炼。为了让代码简练,Java 1.5引入了具有在原始类型和对象类型自动转换的装箱和拆箱机制。但是自动装箱和拆箱并非完美,在使用时需要有一些注意事项,如果没有搞明白自动装箱和拆箱,可能会引起难以察觉的bug。

Integer i=5;//装箱
int n=i;//拆箱

5.2 package 包

包package的作用:

1.组织相关的源代码文件

2.不同包中的类名可以相同,用来避免名字冲突

3.提供包一级的封装和存取权限

定义包名的语法:package 包名;  
    例如:package cn.yuan;
1、定义包的语句必须放在所有程序的最前面
2、包定义语句不是必须的,如果没有定义包,则当前编译单元属于无名包,生成的class文件放在一般与.java文件同目录。
3、Java编译器会自动引入包java.lang,同包下的内容,对于其他的包,如果程序中使用到包中的类,则必须使用import引入。
java 是单继承,多实现,每个java类,都会默认继承Object类,java.lang包下的所有类和同包下的类,在程序运行时,不用导入直接使用,java.lang.String  java.lang.System类都是不用导入,直接使用的,同一个包的类之间相互引用,也不需要导入。

5.3 extends 类的继承

继承 extends

java是单继承,java类不支持多继承

final类没有子类,不能被继承

class B{

}
class A extends B{

}

重载

重载可以出现在一个类中,也可以出现在继承关系中,存在方法名称一样,方法参数不一样。重载对返回类型没有要求,可以相同也可以不同,所以不能通过返回类型是否相同来判断重载

public void print(){}
public void print(int i){}

重写

方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。

只能出现在继承关系中,final方法不能被子类重写

public class Db{
  public void conn(){  
  }
}
public class DbMySQL extends Db{
     public void conn() {
        System.out.println("连接MySQL");
    }
}

5.4 abstract class 抽象类

声明抽象类,使用关键字abstract

package ycs;

public abstract class Db {
    //抽象方法,无方法体,无法直接使用,需要使用他的子类
    public abstract void connection();
    public int pf(int i) {
        return i * i;
    }
}

抽象类

1、抽象类声明时abstract class 类名{}

2、抽象类不能直接实例化,使用抽象类的子类

3、抽象类是可以有抽象方法 abstract void show(); 没有方法体

4、A a = new B();声明B类时继承了A类,A是父类,B是子类

5、抽象方法是不能私有的private修饰

6、有抽象方法的类必须抽象类,抽象类可有抽象方法,也可有普通方法,也可以没有抽象方法

InputStream is = new FileInputStream();

InputStream 是抽象类,FileInputStream类是继承的子类

类 = 静态段 初始化段 属性 方法 class 类名{}

抽象类 = 静态段 初始化段 属性 方法 [抽象方法] abstract class 类名{}

7、抽象更像一种编程规范,一般是项目经理,架构师编写的多。

5.5 interface 接口

接口的声明

public interface Db {}

接口声明使用interface关键字: interface Api{}

java 1.7 之前的接口规则:接口 = 常量 + 抽象方法

java 1.8 接口新定义规则:接口 = 常量(多个) + 抽象方法(多个) + default实现方法(多个) + static实现方法(多个)

java 1.8 接口函数式接口规则: 接口 = 常量 + default实现方法 + static实现方法 + 抽象方法(有且只有一个抽象方法)

定义接口

package cn.yuan.cn;

public interface Db {
    int i = 3;
    void conn();
    static int pf(int i) {
        return i * i;
    }
    default int lf(int i) {
        return i * i * i;
    }
    void read();
}

定义接口实现类

package cn.yuan.cn;

public class DbMySQL implements Db {

    @Override
    public void conn() {
        System.out.println("连接MySQL");
    }

    @Override
    public void read() {

    }
}

函数式接口的概念?

有且只有一个抽象方法的接口,默认是函数式接口,函数式接口可以使用lambda表达式作为方法接口参数来执行。

public interface Db {
   void print();
}

接口和抽象类的区别

函数式接口 java1.8新技术
必须是一个接口,有且只有一个抽象方法,此接口主要用于lambda表达式编程,
接口作为参数

interface MyUtil{
   int pf(int i);
}

前后端配置开发时,

接口 接口使用量大,更加规范,
JDBC组件,架构使用的就是接口,所以JDBC里边几乎全是接口
Connectin 对象不是类 是接口 
接口最终使用的是 implements 实现类 或 lambda表达式


抽象类 最终使用的是子类 可以重写 重载相关的方法

5.6 内部类

内部类的共性内部类分为: 成员内部类、静态嵌套类、匿名内部类(直接new 抽象类,直接new 接口)。

如果是函数式接口,可以使用lambda表达,这样可以避免new 接口产生内部匿名类

(1)、内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号。

(2)、内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的。

(3)、外部类不能直接访问其内部类,想要访问内部类,需实例化内部类

(4)、内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量。

(5)、其他类想要直接访问内部类,可直接实例化内部类,方法如下外部类名.内部类 对象名 = new 外部类().new 内部类();

例:Out.In in = new Out().new In();

如果内部类是静态的,当其他类调用内部类可直接通过类名调用,格式如下:

外部类.内部类 对象名 = new 外部类.内部类()

例:Out.In in2 = new Out.In();

当内部类是静态的,且方法也是静态的,此时可不需实例化对象

外部类.内部类.静态方法名();

package cn.yuan.cn;

public class Outter {
    class Inner {//内部类作为成员,可以添加修饰符private protected
    }

    static class In2 {//静态内部类
    }

    static class We {
    }

    public static class out {
        public static void print(int i) {
            System.out.println(i);
        }
    }

    public static void main(String[] args) {
        //Inner inner=new Inner();//这样会报错,内部类的实例化必须是下面这种格式
        Outter.Inner in = new Outter().new Inner();//静态方法里面实例化内部类的格式
        We we = new We();//静态内部类可以需要外部类引用,也可以直接实例化
        Outter.In2 bb = new Outter.In2();
        In2 aa = new In2();
        Outter o = new Outter();
        class A {//局部内部类

        }
        A a = new A();

        interface B {

        }
        B b = new B() {//匿名内部类
        };
    }

    public static void show() {
        Outter.Inner inn = new Outter().new Inner();//静态方法里面,实例化内部类需要外部类引用
        Outter o = new Outter();
        In2 i = new In2();//静态方法;里面实例化静态内部类可以直接new
    }

}

5.7 hashCode()和toString()

hashCode()

ashCoed 的特性:

(1)HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储地址;

(2)如果两个对象相同, equals方法一定返回true,并且这两个对象的HashCode一定相同;

(3)两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能够说明这两个对象在一个散列存储结构中。

(4)如果对象的equals方法被重写,那么对象的HashCode也尽量重写。

面试题:两个对象的 hashCode()相同,则值一定相同吗?不一定

System.out.println("Ma".hashCode());//2484
System.out.println("NB".hashCode());
/*这两个的hashcode相同,但是值不相同
*/

所以有可能不同的值计算出来的hashCode()相同,但是同一个值的hashCode()是不会变的,所以可以根据字符串值推算出它的hashCode(),但是不能根据hashCode()的值推算出字符串的值。

package ycs;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;

}

class Demo {
    public User getUser() {
        return new User(1, "李四");
    }

    public static void main(String[] args) {
        System.out.println(new Demo().getUser());
        System.out.println(new Demo().getUser().toString());
        System.out.println("ok".hashCode());
        System.out.println("ok".hashCode());
        int[] aa = {1, 2};
        System.out.println(aa.hashCode());
        
        System.out.println("-----------------------------------------");
        String s1 = new String("ok");
        String s2 = new String("ok");
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

        System.out.println("-----------------------------------------");
        User u1 = new User(1, "李四");
        User u2 = new User(1, "李四");
        System.out.println(u1);
        System.out.println(u1 == u2);
        System.out.println(u1.equals(u2));
        System.out.println(u1.hashCode());
        System.out.println(u2.hashCode());

        System.out.println(new int[]{2, 3, 4});
        System.out.println(LocalDate.now());
    }

}
运行结果:
User(id=1, name=李四)
User(id=1, name=李四)
3548
3548
1329552164
-----------------------------------------
false
true
3548
3548
-----------------------------------------
User(id=1, name=李四)
false
true
845601
845601
[I@15aeb7ab
2023-02-18   

作用:

我们平时经常用到map来存储对象,因为map是key,value形式的,它不像list形式的集合可以有顺序的从0开始往集合里放数据,而是随意的放,但是取值的话就很麻烦,因为它存放值的时候没有顺序,所以取值的时候根据key去里面一个一个对比,等找到key相等的值就取出,这样就会造成效率问题。

当我们用到hashCode()可以看到我们将name计算为3373707,age计算为98511,这样的话我们存值的时候就根据计算后的数值进行对应位置的存储,同样当我们get取值的时候再次将key计算为hashCode()值,因为同一个字符串hashCode()值相等,这个时候我们就可以直接根据hashCode()值将对应位置的数据取出,就不需要对key一个一个进行对比了,这样大大提高了效率,这就是为什么有hashCode()存在的原因了。

toString()

System.out.println(new Demo().getUser());
System.out.println(new Demo().getUser().toString());
输出结果:
User(id=1, name=李四)
User(id=1, name=李四)

5.8 enum 枚举

枚举的实例对象固定的,实例是自动new,每个枚举类会自动继承java.lang.Enum抽象类

枚举的声明

package cn.yuan;

public enum ActionEnum {
    save, open, delete, show, search;

    ActionEnum() {
        System.out.println(this);//使用该构造方法的时候,会将这五个对象全部输出,且只会输出一次
    }

}
package cn.yuan;

import lombok.Getter;
import lombok.Setter;

public enum ColorEnum {
    red(404, "error"), green(500, "success"), blue(504, "fail");//这就是枚举一共可以实例化的对象为三个,固定的不会还有其他的对象
    @Getter
    @Setter
    private int code;
    @Getter
    @Setter
    private String message;


    private ColorEnum(int c, String m) {
        //枚举的构造方法默认private,并且必须是private修饰符
        //System.out.println(this);
        this.code = c;
        this.message = m;
    }

    public int pf(int i) {
        return i * i;
    }

}
package cn.yuan;

public class EnumTest {
    public static void main(String[] args) {
        ColorEnum c = ColorEnum.blue;//第一次执行枚举的构造方法的时候会输出所有的对象
        System.out.println(c);
        System.out.println(c.name());
        System.out.println(c.ordinal());//索引号 0 1 2 red green blue
        System.out.println(ColorEnum.green.pf(8));
        System.out.println(c.pf(6));
        System.out.println(ColorEnum.green.getCode());
        System.out.println(ColorEnum.red.getMessage());
        System.out.println("-----------------------------------------");
        //使用枚举
        ActionEnum action = ActionEnum.open;
        System.out.println(action);
        switch (action) {
            case open:
                System.out.println(action);
                break;
            case save:
                break;
            case delete:
                break;
            case search:
                break;
            case show:
                break;

        }
    }
}
运行结果:
blue
blue
2
64
36
500
error
-----------------------------------------
save
open
delete
show
search
open
open

5.9 record 值类型

经过 Java 14,15,16 的不断开发优化反馈,终于 Java 16 我们迎来了 Java 值类型的最终版设计,可以正式在生产使用 Java 值类型相关 API 也就是

但是,使用这个值类型 Record 替代原有的所有 Pojo javabean entity model 类,会遇到很多问题。这些问题包括:

  1. 由于值类型没有原来普通 Object 的对象头等信息,所以对于一些 Object 的特性是不兼容的。
  2. 我们目前使用 Java 开发不可能不使用很多三方 jar 包,各种库。这些库中使用的 Pojo 类型并没有使用值类型。不过,不用太担心,只要这些开源库还比较活跃,那么一定早晚会兼容值类型的。
  3. lombok 可能快速帮助我们生成pojo类

Record 要解决的问题最主要的一点就是,让Java适应现代硬件:在 Java 语言发布之初**,**一次内存访问和一次数字计算的消耗时间是差不多的,但是现在,一次内存访问耗时大概是一次数值计算的 200 ~ 1000 倍。从语言设计上来说,也就是间接访问带来的通过指针获取的需要操作的内存,对于整体性能影响很大。

Java 是基于对象的语言,也就是说,Java 是一种基于指针的间接引用的语言。这个基于指针的特性,给每个对象带来了唯一标识性。例如判断两个 Object 的 ==,其实判断的是两个对象的内存相对映射地址是否相同,尽管两个对象的 field 完全一样,他们的内存地址也不同。同时这个特性也给对象带来了多态性,易变性还有锁的特性。但是,并不是所有对象都需要这种特性。

由于指针与间接访问带来了性能瓶颈,Java 准备对于不需要以上提到的特性的对象移除这些特性。于是乎Record 出现了。

package cn.yuan.cn;

public record Book(int id, String name, String author) {//相当于三个参数的构造方法

    Book() {//无参的构造方法
        this(10, null, null);
    }

    Book(int id) {//一个参数的构造方法
        this(id, null, null);
    }

    public static void main(String[] args) {
        Book b = new Book(100, "java", "李四");
        
        System.out.println(b);
        System.out.println(b.id());
        System.out.println(new Book(20));
        System.out.println(new Book());

    }
}
运行结果:
Book[id=100, name=java, author=李四]
100
Book[id=20, name=null, author=null]
Book[id=10, name=null, author=null]

这样编写代码之后,Record 类默认包含的元素和方法实现包括:

  1. record 头指定的组成元素(int id, String name, String author),并且,这些元素都是 final 的。

  2. record 默认只有一个构造器,是包含所有元素的构造器。

  3. record 的每个元素都有一个对应的 getter(但这种 getter 并不是 getxxx(),而是直接用变量名命名,所以使用序列化框架,DAO 框架都要注意这一点)

  4. 实现好的 hashCode(),equals(),toString() 方法(通过自动在编译阶段生成关于 hashCode(),equals(),toString() 方法实现的字节码实现)。

  5. 不能用 abstract 修饰 Record 类,会有编译错误。 可以用 final 修饰 Record 类,但是这其实是没有必要的,因为 Record 类本身就是 final 的。

    成员 Record 类,还有本地 Record 类,本身就是 static 的,也可以用 static 修饰,但是没有必要。

    和普通类一样,Record 类可以被 public, protected, private 修饰,也可以不带这些修饰,这样就是 package-private 的。

    和一般类不同的是,Record 类的直接父类不是 java.lang.Object 而是 java.lang.Record。但是,Record 类不能使用 extends,因为 Record 类不能继承任何类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值