目录
一. 对面向对象的基本认知
1.1 什么是面向对象
1.2 面向对象与面向过程的区别
二. 类的定义和使用
2.1 类的定义与注意事项
2.2 类的实例化
2.3 this关键字
三. 对象的构造和初始化
3.1 就地初始化
3.2 构造方法初始化
3.3 对象的打印
四. 包
4.1 包的基本使用
4.2 自定义包
4.3 包的访问权限控制
五. 封装(面向对象核心特性之一)
5.1 何为封装
5.2 封装的实现方式
5.3 访问修饰限定符详解
六. static关键字
6.1 static修饰成员变量
6.2 static修饰成员方法
6.3 理解为什么静态的上下文(方法)中不能访问任何非静态的(重要)
七. 代码块
7.1 普通代码块
7.2 实例代码块
7.3 静态代码块
八. 总结
一. 对面向对象的基本认知
1.1 什么是面向对象
众所周知,Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),而在面向对象的世界中,一切皆对象。面向对象是解决问题的一种思想,依靠对象之间的交互完成一件事情。 面向对象思维,其实就是结果导向思维,只关注结果,不关注过程,为达目的不择手段。
1.2 面向对象与面向过程的区别
接触Java前,或许有的人接触过C语言,C语言就是一门面向过程的语言。 至于这两者之间的关系,以洗衣服为例: 面向过程,更注重的是洗衣服的过程,而洗衣服的过程,通常都需要很多个步骤,而面向过程就是把程序看作一系列顺序执行的步骤,以流程和函数为中心,通过参数或全局变量处理数据,这种方式开发的程序,将来在扩展或维护方面会比较麻烦。比较适合算法类任务、一次性脚本、性能敏感的底层开发。(比如C) 面向对象,不关注洗衣服的过程,底层衣服是如何洗的无需关注,和现在使用洗衣机洗衣服是一样的,我们只需要通过对象之间的交互,即可完成洗衣服这件事。面向对象以类和对象为中心,把数据与操作封装在一起,通过封装继承多态管理复杂度。比较适合复杂业务系统、多人协作、长期维护项目。(比如Java,Python)
面向过程和面向对象并不是一门语言,而是解决问题的思想,没有好坏之分,均有其专门的应用场景。
二. 类的定义和使用
Java中的类,主要用来对一个实体(对象)进行描述,比如这个对象里面具体有哪些属性(外观,尺寸等),哪些功能(能做什么),相当于设计图 / 模板 / 模子
2.1 类的定义与注意事项
在Java中定义类的关键字为class,其基本写法如下
// 创建类
class ClassName{
field; // 字段(属性) 或者 成员变量
method; // 行为 或者 成员方法
}
class为定义类的关键字,ClassName为类的名字,{}中为类的主体。
类中包含的内容称为类的成员。类中的内容主要分为两部分:属性和方法 属性主要是用来描述类的,称之为类的成员属性或者类成员变量。 方法主要说明类具有哪些功能,称为类的成员方法。 下面是一个简单的示例:
public class Dog {
public String name;
public int age;
public String color;
public void bark() {
System.out.println("一只名为"+name+"的狗在叫");
}
public void sleep() {
System.out.println(name+"睡觉");
}
}
//关于上面的修饰符,如public, static等,会在后文详细介绍
2.2 类的实例化
定义了一个类,就相当于在计算机中定义了一种新的类型。 用类的类型去创建对象的过程,叫做类的实例化。 与前面的int,double类似,不过这些是Java语言自带的类型,而类相当于是用户自定义了一个新的类型(如上面的Dog类型)(牢记:Java中除了基本数据类型之外,其余全部都是引用类型)
Java中通过new关键字,配合类名来实例化对象,以上面Dog类为例:
Dog d = new Dog();
此时就说,创建了一个Dog类型的对象,一个类可以通过new关键字,实例化无数个对象。
实例化后可以通过.(成员访问操作符)来访问类中的成员,若不对成员变量进行初始化,则默认值与数组初始化时默认值一样。(这里不会报错,局部变量不初始化使用才会报错),比如:
public class Main{
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "张三";
dog.age = 20;
dog.color = "雌";
System.out.println("姓名:"+dog.name);
System.out.println("年龄:"+dog.age);
System.out.println("颜色:"+dog.color);
dog.bark();
}
}
再次理解类和对象:
2.3 this关键字
在成员方法中,通常会遇到形参名与变量名相同,此时要明确区分方法中要操作的是哪个变量 (成员变量还是局部变量),为了解决这个问题,从而有了this关键字
this是“成员方法”的一个隐藏的参数,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收。
public class Date() {
public int year;
public int month;
public int day;
public void setDay(int day) {
this.day = day;
}
//这里可以看作:
/*
public void setDay(Date this,int day) {
this.day = day;
}
*/
}
也就是说,this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有对成员变量的操作,都是通过该引用去访问。只不过这个过程编译器会自动完成,不需要用户来
public class Person {
public String name;
public int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Person p = new Person("李白",18);
//此时this指向的就是p这个引用,因为我们是通过p调用的成员方法
}
}
关键点在于this代表的是当前对象的引用,当前对象就是指:哪个对象调用Person方法,谁就是this引用!
this引用的特性:
三. 对象的构造和初始化
前面我们知道,局部变量如果不初始化,使用的时候会报错 编译失败 而成员变量如果不初始化,则会初始化为默认值 成员变量的初始化,有两种方式:就地初始化和构造方法初始化
3.1 就地初始化
在声明成员变量时,就直接给初始值
public int day = 1;
3.2 构造方法初始化
构造方法(也称构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
调用构造方法是为了干什么? ——用来初始化这个对象
IDEA中生成构造方法的快捷方式:
要生成的地方鼠标右键点击Generate(快捷键为Alt+Insert),在弹出的选项中选择Constructor(构造方法),若有两个成员变量,则可以选择生成哪个,按Ctrl键多选,不选则生成无参数的构造方法。
生成无参数的构造方法时,我们不传参,但是也可以在里面设置默认值。
public class Person {
public String name;
public int age;
public Person() {
this.name = "lisa";
this.age = 20;
System.out.println("不带参数的构造方法");
}
public Person(String name,int age) {
this.name = name;
this.age = age;
System.out.println("带有2个参数的构造方法");
}
public static void main(String[] args) {
//调用构造方法
Person p1 = new Person();
Person p2 = new Person("张三",20);
}
}
构造方法注意事项:
public int year;
public int month;
public int day;
public Date(){
this(1900, 1, 1);
}
// 带有三个参数的构造方法
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
3.3 对象的打印
如果我们直接打印对象的引用,此时输出的结果为:类路径名@对象的hashcode值。(即该对象的地址),原因是println方法执行的底层逻辑为
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
我们可以通过在类中重写toString方法,来让println默认打印对象中的属性 我们重写toString方法后,执行println方法,就会优先调用自己写的这个toString方法
这个toString也有快捷写法: 在Idea中,在类中空白位置右键,选Generate,然后选择toString() 将要打印的内容勾选上即可,编译器会自动写出类名,类中的对象的值等。快捷键为Alt+insert,重写toString示例如下:
public class Person {
public String name;
public int age;
public Person() {
this.name = "lisa";
this.age = 20;
System.out.println("不带参数的构造方法");
}
public Person(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\\'' +
", age=" + age +
'}';
}
}
四. 包
4.1 包的基本使用
包其实就是我们常见的文件夹,将各种文件组织到一个地方。
定义:(Package)是Java中一个组织类的方式,用于防止类名冲突和提高代码的可读性。 在Java中,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式。
导包: 如我们前面用的import java.util.Arrays; import java.util.Scanner; 前面java.util就是包,Arrays,Scanner则是这个包下的类。
导包有三种形式
一般情况下更建议显式的指定要导⼊的类名.而不是使用通配符(*)来导包,否则还是容易出现冲突的情况: 比如用通配符导入了如下两个包: import java.util.*;和import java.sql.*; 此时在下面写代码时要使用Date(日期)类,Date date = new Date(); 此时就会报错,原因是Date类很特殊,java.util和java.sql中都有名为Date的类,编译器不知道引用哪个
一种比较少见的导包方式:
⽤import static导⼊包中静态的⽅法和字段。
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double x = 30;
double y = 40;
//静态导⼊的⽅式写起来更⽅便⼀些.
// double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
double result = sqrt(pow(x, 2) + pow(y, 2)); //在最上面导这个静态的包后Math就可以省略。
System.out.println(result);
}
}
虽然有这种写法,但是非常不建议这么用,原因是会导致代码很难看懂。
实际开发中
只要一个类在一个文件夹(包)下面,(比如Arrays,就在java.util这个文件夹(包)下)
看源码的时候,就会发现在其前方总会有这么一行代码:package java.util;
这句代码的意思是:声明当前java类是在哪个包(文件夹)底下的
如果想自己去手动导入,不在写代码的时候自动生成,又不知道这个类是在哪个包(文件夹)下的,那就直接进入这个类,比如Ctrl进入Arrays,看源码最上方package后面的内容。
如Math,就可以看到在其最上方是package java.lang; 此时你如果要自己导入,则是java.long.Math; 且,一般在java.long包下面的类,我们不去导入,因为系统会默认导入
src文件夹,也不需要导包,因为src属于一个默认的包,程序运行起来时都会默认在src下找这个程序,所以直接定义在src下的类,无需导包,但若src下面还有文件夹,文件夹中定义类的时候,就必须在上面进行声明了
4.2 自定义包
基本规则
1.在⽂件的最上⽅加上⼀个package语句指定该代码在哪个包中.
2.包名需要尽量指定成唯⼀的名字,通常会⽤公司的域名的颠倒形式(例如com.baidu.www)
3.包名要和代码路径相匹配.例如创建com.ex.demo1的包,那么会存在⼀个对应的路径com/ex/demo1来存储代码.
4.如果⼀个类没有package语句,则该类被放到⼀个默认包中.
5.包名尽量全部用小写,尽量不包含中文(包含了也不会报错,但是为了避免各种问题) 形式就跟写路径一般。
使用IDEA创建包的过程
1.在IDEA中先新建⼀个包:右键src->新建->包
2.在弹出的对话框中输⼊包名,例如com.snow.demo
3.在包中创建类,右键包名->新建->类,然后输⼊类名即可.
4.此时的代码中会出现package语句 package com.snow.demo;
Java提供的一些常用的包:
java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。 java.lang.reflect:java 反射编程包; java.net:进行网络编程开发包。 java.sql:进行数据库开发的支持包。 java.util:是java提供的工具程序包。(集合类等) 非常重要 java.io:I/O编程开发包。
4.3 包的访问权限控制
包的访问权限由访问修饰限定符控制(后续详细介绍),如果我们在定一个类的时候,如果没有指定成员变量/成员方法的访问权限,此时默认的访问权限就是包访问权限(default)。 包访问权限只能在同一个包中访问,跨包无法访问。
五. 封装(面向对象核心特性之一)
5.1 何为封装
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。 何为封装?简单来说就是套壳屏蔽细节,将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。这就是封装 封装有很大一个好处,就是使用者(用户)无需关心其代码实现底层细节,可以很方便的进行调用,大大降低上手成本。
5.2 封装的实现方式
Java中可以用private这个关键字来实现封装的效果。 被private关键字修饰后,当前这个属性就只能在当前类中使用了,不可以在类外使用
class Student{
private String name; //使用 private 修饰
private int age; //使用 private 修饰
public void eat() {
System.out.println(this.name + " 正在吃饭!");
}
public void show() {
System.out.println("name: "+name+" 年龄:"+age);
}
}
public class Test {
public static void main(String[] args) {
Student stu = new Student();
}
}
上面代码成员变量使用private修饰后,在Test类中无法通过stu引用来进行访问数据,这就是封装 所以封装,就是对类的实现细节进行了隐藏,从而提高了属性的安全性 若想要访问,此时需要Student类手动提供get和set方法,作为公开的接口来进行访问
class Student{
private String name; //使用 private 修饰
private int age; //使用 private 修饰
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void eat() {
System.out.println(this.name + " 正在吃饭!");
}
public void show() {
System.out.println("name: "+name+" 年龄:"+age);
}
}
public class Test {
public static void main(String[] args) {
Student stu= new Student();
stu.setAge(10);
System.out.println(person.getAge());
}
}
PS:这些get和set方法,也可以直接借助编译器生成,仍然是Generate(Alt+Insert)
封装的好处:
5.3 访问修饰限定符详解
Java主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,访问权限用来控制方法或者字段能否直接在类外使用。 Java中提供了四种访问修饰限定符
| public | ✔️ | ✔️ | ✔️ | ✔️ |
| protected | ✔️ | ✔️ | ✔️ | |
| default | ✔️ | ✔️ | ||
| private | ✔️ |
public修饰的成员变量,在所有情况下均是可以正常访问的 private修饰的成员变量,只能在当前类中来使用
default表示什么修饰符都不加,此时只能在包当中才能被访问 protected 只有在不同包中的非子类情况下不能被访问,这部分要理解需要继承相关知识,
简单来说就是两个包下,没有继承关系的两个类,这种情况是不能访问的,其余都可以
六. static关键字
static关键字可以修饰成员变量和成员方法
6.1 static修饰成员变量
被static修饰的成员变量,叫做静态成员变量,与前面成员变量的不同之处在于:
对于成员变量,每个对象中,都会包含一份(所以也被称作实例变量) 对于静态成员变量,也可以称作类成员变量(类变量),不属于某个具体的对象,每个类只有一份,给这个类中所有对象共享。
静态成员变量特点(非常重要):
代码示例:
class Student{
private String name;
private int age;
public static String classRoom = "1-101";
}
public class Test {
public static void main(String[] args) {
// 静态成员变量可以直接通过类名访问
System.out.println(Student.classRoom);
Student stu1 = new Student();
Student stu2 = new Student();
Student stu3 = new Student();
// 也可以通过对象访问:但是classRoom是三个对象共享的
System.out.println(stu1.classRoom);
System.out.println(stu2.classRoom);
System.out.println(stu3.classRoom);
}
}
6.2 static修饰成员方法
被static修饰的成员方法叫静态成员方法,与静态成员变量一样,静态成员方法是类的方法,不是某个对象所特有的。静态成员变量一般通过静态方法来访问。
public class Student{
private static String classRoom = "001班";
public static String getClassRoom(){
return classRoom;
}
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.getClassRoom());
}
}
静态成员方法特点:
System.out.println(this.age);
return classRoom;
}
// 编译失败:Error:(35, 28) java: 无法从静态上下文中引用非静态 变量 this
public static String getClassRoom(){
age += 1;
return classRoom;
}
// 编译失败:Error:(35, 9) java: 无法从静态上下文中引用非静态 变量 age
doClass(); //该访问为非静态方法
return classRoom;
}
// 编译报错:Error:(35, 9) java: 无法从静态上下文中引用非静态 方法 doClass(
6.3 理解为什么静态的上下文(方法)中不能访问任何非静态的(重要)
看下面一段代码
class Student{
public String name;
public int age;
public static String classRoom;
public static String getClassRoom() {
return classRoom;
//return this.classRoom;——error…
//return this.name;——error…
//return name;——error…
}
}
public class Test {
public static void main(String[] args) {
System.out.println(Student.getClassRoom());
}
}
为什么静态方法中,不能包含任何非静态的内容?
1. 调用这个静态方法的时候,此时并没有实例化对象,而上面name, age, this, 这些都是要依赖对象才能进行访问的
2. 静态方法的调用不依赖于对象,但方法内部又使用了依赖于对象的内容,所以这里不能调用任何非静态内容。
3. 非静态成员方法中可以使用this,是因为有一个隐式参数会自动加在该方法的参数列表中,而静态成员方法不会有这个变量,所以不能使用this
这部分最重要的一句就是:静态的里面不能有非静态的,而非静态的里面可以有静态的(依赖对象的调用一个不依赖对象的,这是完全可以的)
总结原因就是:静态的不依赖实例对象,而非静态的依赖。
七. 代码块
使用{}定义的一段代码称为代码块。根据代码块定义位置以及关键字,可分为以下四种:
7.1 普通代码块
定义在方法中的代码块就是普通代码块
public class Main{
public static void main(String[] args) {
{ //直接使用{}定义,普通方法块
int x = 10 ;
System.out.println("x1 = " +x);
}
int x = 100 ;
System.out.println("x2 = " +x);
}
}
7.2 实例代码块
定义在类中的代码块(不加修饰限定符)就是实例代码块,实例代码块一般用于初始化实例成员变量
public class Student {
public String name;
public int age;
public static String classRoom;
public Student() {
System.out.println("不带参数的构造方法被执行了");
}
{
this.name = "lisa";
this.age = 18;
System.out.println("实例代码块执行了,一般用来初始化实例数据成员…");
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student();
System.out.println(student1.name);
System.out.println(student1.age);
}
}
观察上述代码,我们可以发现实例代码块的执行顺序,还在构造方法之前。(关于执行顺序后续会详细讲解)
1. 实例代码块一般用于初始化实例成员变量,且实例代码块比构造方法更先执行
2. 每次实例化对象时,实例代码块都会执行一次,与构造方法一样。
3. 使用带参数的构造方法时,由于构造方法晚于实例代码块执行,所以构造方法可能会把实例代码块赋的值覆盖掉。
7.3 静态代码块
使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。
public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom;
//实例代码块
{
this.name = "tom";
this.age = 12;
this.gender = "man";
System.out.println("I am instance init()!");
}
// 静态代码块
static {
classRoom = "1-301";
System.out.println("I am static init()!");
}
public Student(){
System.out.println("I am Student init()!");
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
}
}
观察上面程序结果,可以发现,静态代码块会最先被执行。
而关于创建静态成员变量的代码与静态块的执行顺序,与实例代码块相同,都是谁在上面谁先被执行到。
注意事项:
- 静态代码块不管生成多少个对象,其只会执行一次 static随着类加载而加载,而类只会加载一次
- 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
- 实例代码块只有在创建对象时才会执行
代码块执行顺序:
静态代码块(静态成员变量) -> 实例代码块(实例成员变量) -> 构造方法
八. 总结
本文讲述了Java类和对象相关知识,本篇文章还是以基础扫盲为主,方便速查遗忘知识。



