文章目录
1️⃣认识类和对象
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
JAVA是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
面向过程注重的是过程,在整个过程中所涉及的行为,就是功能。
面向对象注重的是对象,也就是参与过程所涉及到的主体。是通过逻辑将一个个功能实现连接起来
【面向对象概念】
1.面向对象是思考问题的一种思考方式,是一种思想。
2.类就是一类对象的统称。对象就是这一类具体化的一个实例。
3.面向对象的好处:将复杂的事情变简单了,只要面对一个对象就行。
【面向对象设计】
面向对象设计把握一个重要的经验:谁拥有数据,谁对外提供操作这些数据(私有)的方法!
(被动的一方是数据的拥有者,主动的一方是执行者)
开发时:找对象,建对象,用对象,并维护对象之间的关系。
简而言之
面向对象就是用代码(类)来描述客观世界的事物的一种方式. 一个类主要包含一个事物的属性和行为
2️⃣类和类的实例化
简单的例子:我们做月饼的模子就是一个类,而通过这个模子可以做出月饼,那么在这个例子当中,类就是那个模子,而月饼就是那个对象,所以月饼就是一个实体。一个模子可以实例化无数个对象。
总的来说:类相当于一个模板,规定了该类所具备的属性和行为,对象是由模板产生的样本。一个类,可以产生无数的对象。
声明一个类就是创建一个新的数据类型,而类在Java 中属于引用类型, Java 使用关键字class 来声明类。
基本语法
// 创建类
class class_name{
field;//成员属性
method;//成员方法
}
// 实例化对象
class_name 对象名 = new class_name();
- 类中的元素称为:成员属性。类中的函数称为:成员方法。
- 类命名使用大驼峰命名法,每个单词首字母大写,不需要用
_
间隔。
示例:
class Person {
public int age;//成员属性 实例变量
public String name;
public String sex;
public void eat() {//成员方法
System.out.println("吃饭!");
}
public void sleep() {
System.out.println("睡觉!");
}
}
public class Main{
public static void main(String[] args) {
Person person = new Person();//通过new实例化对象
person.eat();//成员方法调用需要通过对象的引用调用
person.sleep();
//产生对象 实例化对象
Person person2 = new Person();
Person person3 = new Person();
}
}
输出结果
吃饭!
睡觉!
注意事项
new
关键字用于创建一个对象的实例.- 使用
.
来访问对象中的属性和方法. - 同一个类可以创建对多个实例.
3️⃣类的成员
类的成员可以包含以下:字段、方法、代码块、内部类和接口等。
接口在这里不展开讲述。
1.字段/属性/成员变量
class Person {
private String name ;//属性
private int age = 10;//属性
private String gender;//字段,成员变量
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 float getHeight() {
return 1.75f;
}
public void setHeight(float height) {
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.getName());
System.out.println(person.getAge());
}
}
//输出结果
null //没有初始化,为默认值
10
Person
类中定义了3个字段 age
、name
、gender
它们是类成员变量,但它们不全是属性;那什么是属性?
属性的定义规则是:set/get
方法名,去掉set/get
后,将剩余部分首字母小写得到的字符串就是这个类的属性。
所以呢name、age、height就是Person类的属性。(注:尽管这个height不是成员变量,但当外部类调用Person对象时还会有height这个属性)。
总结
- 如果单纯地包含变量的时候,就叫做字段/成员变量.
- 如果字段有各自的
set/get
方法,那么就叫做属性. - 如果没有字段只有
set/get
方法,也叫属性.
默认值规则
- 对于各种数字类型, 默认值为
0
.- 对于 boolean 类型, 默认值为
false
.- 对于引用类型(String, Array, 以及自定制类), 默认值为
null
2.普通方法和构造方法
普通方法
class Person {
public int age = 18;
public String name = "张三";
public void show() {
System.out.println("我叫" + name + ", 今年" + age + "岁");
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.show();
}
}
// 执行结果
我叫张三, 今年18岁
此处的 show
方法, 表示 Person
这个对象具有一个 “展示自我” 的行为.
这样的 show
方法是和person
实例相关联的. 如果创建了其他实例, 那么 show
的行为就会发生变化
Person person2 = new Person();
person2.name = "李四";
person2.age = 20;
person2.show();
// 执行结果
我叫李四, 今年20岁
构造方法
在实例化对象的时候会被自动调用到的方法, 方法名字和类名相同, 用于对象的初始化.
虽然我们前面已经能将属性就地初始化, 但是有些时候可能需要进行一些更复杂的初始化逻辑, 那么就可以使用构造方法.
构造方法是一种特殊方法, 使用关键字new实例化新对象时会被自动调用, 用于完成初始化操作.
new 执行过程
- 为对象分配内存空间
- 调用对象的构造方法
语法规则
- 方法名称必须与类名称相同
- 构造方法没有返回值类型声明
- 每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造)
注意事项
- 如果类中没有提供任何的构造函数,那么编译器会默认生成一个不带有参数的构造函数
- 若类中定义了构造方法,则默认的无参构造将不再生成.
- 构造方法支持重载. 规则和普通方法的重载一致.重载的构造方法按参数个数从上往下排序.
- 构造方法是给类中的属性赋值,类中成员值在类定义时就规定好了,未赋值的属性为默认值.
- 构造方法可被
private
修饰,构造方法一旦被private
修饰,这个类就不能直接通过该构造方法产生对象。而是类内部提供方法产生对象,不希望通过外部产生对象,外部只能使用,不能创建,对象的数量有限。 - Java中没有析构函数
代码示例
class Person {
private String name;//实例成员变量
private int age;
private String sex;
//默认构造函数 构造对象
public Person() {
this.name = "caocao";
this.age = 10;
this.sex = "男";
}
//带有3个参数的构造函数
public Person(String name,int age,String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void show(){
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Test{
public static void main(String[] args) {
Person p1 = new Person();//调用不带参数的构造函数 如果程序没有提供会调用不带参数的构造函数
p1.show();
Person p2 = new Person("zhangfei",80,"男");//调用带有3个参数的构造函数
p2.show();
}
}
// 执行结果
name: caocao age: 10 sex: 男
name: zhangfei age: 80 sex: 男
3.this关键字
this
表示当前对象引用(注意不是当前对象). 可以借助 this
来访问对象的字段和方法.
刚才在上述构造方法已经使用this
关键字修饰属性了
this
修饰属性,表示直接从本类中寻找属性
就近匹配原则:编译器会寻找调用位置this
修饰方法,表示调用本类方法
调用本类的普通方法:this.方法名称(参数)
;调用构造方法:this(参数)
this
表示当前对象引用
当前调用的成员方法或变量是通过对象A
调用的,则此时this
就是A
构造方法的相互调用
注意:
- 调用构造方法的语法是:
this(参数)
- 必须放在该方法的第一行
- 不能递归调用构造方法
4.static关键字修饰字段和方法
此处只展开讲述static
修饰字段和方法,修饰代码快和类在后面讲解。
修饰属性
Java静态属性和类相关, 和具体的实例无关. 换句话说, 同一个类的不同实例共用同一个静态属性.
class TestDemo{
public int a;
public static int count;
}
public class Main{
public static void main(String[] args) {
TestDemo t1 = new TestDemo();
t1.a++;
TestDemo.count++;
System.out.println(t1.a);
System.out.println(TestDemo.count);
System.out.println("============");
TestDemo t2 = new TestDemo();
t2.a++;
TestDemo.count++;
System.out.println(t2.a);
System.out.println(TestDemo.count);
}
}
输出结果为:
1
1
============
1
2
- 静态属性在JVM方法存储.
- 局部变量不能用
static
修饰,只能在类中 - 表示类属性,该类的所有对象共享该属性,可以并建议通过类名称进行访问,例:
Person.name
- 当引用为
null
,仍然可以访问类的静态变量
修饰方法
如果在任何方法上应用static
关键字,此方法称为静态方法。
- 静态方法属于类,而不属于类的对象。
- 可以直接通过类名调用静态方法,而无需创建类的实例。
- 实际上一个方法具体要不要带
static
, 都需要是情形而定. main
方法为static
方法.
class TestDemo{
public int a;
public static int count;
public static void change() {
count = 100;
//a = 10; error 不可以访问非静态数据成员
}
}
public class Main{
public static void main(String[] args) {
TestDemo.change();//无需创建实例对象 就可以调用
System.out.println(TestDemo.count);
}
}
输出结果:
100
注意: 静态方法和实例无关, 而是和类相关. 因此这导致了两个情况:
- 静态方法不能直接使用非静态数据成员或调用非静态方法(非静态数据成员和方法都是和实例相关的).
- 用
static
修饰的部分都可以相互调用,并且不需要用对象调用. this
和super
两个关键字不能在静态上下文中使用(this
是当前实例的引用,super
是当前实例父类实例的引用, 也是和当前实例相关).
4️⃣封装
软件开发的本质就是对程序复杂程度的管理. 如果一个软件代码复杂程度太高, 那么就无法继续维护. 如何管理复杂程度? 封装就是最基本的方法.
在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者.
封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了.
这样就降低了类使用者的学习和使用成本, 从而降低了复杂程度.
1.private实现封装
- 被
private
修饰的成员变量和成员方法只在类的内部可见,出了该类就无法使用,继承也无法使用. - 类的使用者根本不需要知道, 也不需要关注一个类都有哪些
private
的成员. 从而让类调用者以更低的成本来使用类. - 阿里编码规约:类中如无特殊说明,所有成员变量统一用
private
封装.
使用 public
class Person {
public String name = "张三";
public int age = 18;
}
class Test {
public static void main(String[] args) {
Person person = new Person();
System.out.println("我叫" + person.name + ", 今年" + person.age + "岁");
}
}
// 执行结果
我叫张三, 今年18岁
- 这样的代码导致类的使用者(
main
方法的代码)必须要了解Person
类内部的实现, 才能够使用这个类. 学习成本较高 - 一旦类的实现者修改了代码(例如把
name
改成myName
), 那么类的使用者就需要大规模的修改自己的代码, 维护成本较高.
使用 private
class Person {
private String name = "张三";
private int age = 18;
public void show() {
System.out.println("我叫" + name + ", 今年" + age + "岁");
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.show();
}
}
// 执行结果
我叫张三, 今年18岁
- 此时字段已经使用
private
来修饰. 类的调用者(main
方法中)不能直接使用. 而需要借助show
方法. 此时类的使用者就不必了解Person
类的实现细节. - 同时如果类的实现者修改了字段的名字, 类的调用者不需要做出任何修改(类的调用者根本访问不到
name
,age
这样的字段).
那么问题来了~~ 类的实现者万一修改了 public 方法 show 的名字, 岂不是类的调用者仍然需要大量修>改代码嘛?
这件事情确实如此, 但是一般很少会发生. 一般类的设计都要求类提供的 public 方法能比较稳定, 不应该频繁发生大的改变. 尤其是对于一些基础库中的类, 更是如此. 每次接口的变动都要仔细考虑兼容性问题.
注意事项:
- 通常情况下我们会把字段设为
private
属性, 但是方法是否需要设为public
, 就需要视具体情形而定. 一般我们希望一个类只提供 “必要的”public
方法,而不应该是把所有的方法都无脑设为 public. - 在一个
.java
文件中总有一个类被public
修饰.但是其他的类没有被修饰.因为用public
修饰类,表明这类可被外部使用的权限,而在这个.java
文件里的其他类不加访问修饰符,只在该文件中的其他类可用,不能被外部调用。
这样可以较好地实现封装。同时,被public修饰的类,其类名称必须和文件名保存一致。 private
只能修饰内部类,外部类不可行,因为类定义是为了产生对象。
2.getter和setter方法
当我们使用 private
来修饰字段的时候, 就无法直接使用这个字段了.
此时如果需要获取或者修改这个private
属性, 就需要使用 getter / setter
方法.
class Person {
private String name;//实例成员变量
private int age;
public void setName(String name){
//name = name;//不能这样写
this.name = name;//this引用,表示调用该方法的对象
}
public String getName(){
return name;
}
public void show(){
System.out.println("name: "+name+" age: "+age);
}
}
public static void main(String[] args) {
Person person = new Person();
person.setName("caocao");
String name = person.getName();
System.out.println(name);
person.show();
}
// 运行结果
caocao
name: caocao age: 0
注意事项:
- IDEA中使用快捷键
Alt + insert
,可快捷生成getter
和setter
方法 getName
即为getter
方法, 表示获取这个成员的值.setName
即为setter
方法, 表示设置这个成员的值.- 当
set
方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this
, 相当于自赋值.this
表示当前实例的引用. - 不是所有的字段都一定要提供
setter / getter
方法, 而是要根据实际情况决定提供哪种方法.
5️⃣代码块
字段的初始化方式有:
- 就地初始化
- 使用构造方法初始化
- 使用代码块初始化
前两种方式前面已经学习过了, 接下来我们介绍第三种方式, 使用代码块初始化.
1.什么是代码块
使用{}
定义的一段代码.
作用域就在{}
中
根据代码块定义的位置以及关键字,又可分为以下四种:
- 普通代码块
- 构造块
- 静态块
- 同步代码块(多线程部分的内容,此处先跳过)
2.普通代码块
定义:在方法中的代码块.
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);
}
}
// 执行结果
x1 = 10
x2 = 100
这种用法比较少见
3.构造代码块
- 用
{}
直接定义在类中的代码块 - 当产生对象时执行构造代码块,优先于构造方法执行
- 有几个对象产生构造代码块就执行几次
class Person{
private String name;//实例成员变量
private int age;
private String sex;
public Person() {
System.out.println("I am Person init()!");
}
//实例代码块
{
this.name = "bit";
this.age = 12;
this.sex = "man";
System.out.println("I am instance init()!");
}
public void show(){
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person();
p1.show();
}
}
// 运行结果
I am instance init()!
I am Person init()!
name: bit age: 12 sex: man
4.静态代码块
使用static
定义的代码块。一般用于初始化静态成员属性
class Person{
private String name;//实例成员变量
private int age;
private String sex;
private static int count = 0;//静态成员变量 由类共享数据 方法区
public Person(){
System.out.println("I am Person init()!");
}
//实例代码块
{
this.name = "bit";
this.age = 12;
this.sex = "man";
System.out.println("I am instance init()!");
}
//静态代码块
static {
count = 10;//只能访问静态数据成员
System.out.println("I am static init()!");
}
public void show(){
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();//静态代码块是否还会被执行?
}
}
注意事项:
- 静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的。
- 静态代码块执行完毕后, 实例代码块(构造块)执行,再然后是构造函数执行。
- 静态代码块优先于
main
方法执行
6️⃣内部类
所谓的内部类,就是将类结构的定义套在另一个类的内部。
现实生活中,比如心脏和人体的关系 - 心脏这个类套在人体类的内部。
内部类一共分为以下四种:
- 成员内部类(类比成员方法)
- 静态内部类(类比静态方法)
- 方法内部类
- 匿名内部类 - Lambda表达式的前身
1.成员内部类
定义
直接定义在类中,不加任何static
定义的类,有访问权限。
public class Outter {
//心脏属于私有内部类,对外部完全隐藏,只是在类的内部使用
private class Inner{
}
}
使用方法
a. 成员内部类的创建需要依赖外部类对象,在没有外部类对象之前,无法创建成员内部类对象(心脏就是一个成员内部类,在没有人体的情况下,是无法直接创建心脏这个对象)
b. 内部类是一个相对独立的实体,与外部类不是is - a
关系(心脏is not a
人)
c. 内部类可以直接访问外部类的字段和方法(包括私有域),内部类中隐藏了一个外部类对象;
外部类必须通过内部类的对象来访问内部类的字段和方法(包括私有域)
d. 成员内部类对象的创建
1.在外部类的内部创建(和使用其他的普通类没啥区别)
内部类名称 内部类引用 = new 内部类();
Inner inner = new Inner();
2.在外部类的外部创建内部类对象(内部类要对外部可见)
外部类名称.内部类 引用= new 外部类().new 内部类();
Outter.Inner inner = new Outter().new Inner();
- 由于成员内部类
Inner
是套在Outter
之中的,就相当于Outter
的一个成员变量,因此访问Inner
类,就使用Outter.Inner
的引用访问. - 成员内部类是需要依附外部类,先要产生外部类对象才能产生内部类对象.
心脏和人体的关系,先有人体才能有心脏
new Outter()
-> 产生了一个外部类的对象
然后通过产生好的外部类对象new Outter.
再产生内部类对象new Inner()
public class Outter1 {
private int msg = 99;
public class Inner{
private int num = 10;
//直接访问外部类的msg属性
public void test(){
System.out.println(msg);
//内部类中隐藏了一个外部类对象
System.out.println(Outter1.this.msg);
}
}
public void fun() {
//外部类的内部需要通过内部类的对象访问内部类的私有属性
Inner inner = new Inner();
//访问Inner类的私有属性
inner.test();
}
public static void main(String[] args) {
//外部类的外部和main方法使用规则
Outter1.Inner inner = new Outter1().new Inner();
inner.test();
}
}
注意事项
成员方法能访问静态域,不能定义静态属性
对于成员内部类来说,能否定义静态属性?
不能,因为内部类依赖于外部类,如果成员内部类有静态属性,那么没有外部类对象也能访问,所以是不予许的。
对于外部类来说,能否在外部类的静态方法中使用成员内部类?
不能,相当于在静态方法中使用成员变量。
比如说在main
方法中使用内部类,包括定义。
2.静态内部类
定义
定义在类中,使用static
修饰的内部类就是静态内部类。
public class Outter {
public static class Inner{
}
}
使用方法
a. 静态内部类不需要依附外部类对象!
b. 静态内部类对象的创建
-
外部类的内部(和普通类的用法一致)
内部类名称 内部类引用 = new 内部类();
Inner inner = new Inner();
-
在外部类的外部创建内部类对象(此时不需要创建外部类对象)
外部类名称.内部类 引用= new 外部类.内部类();
Outter2.Inner inner = new Outter2.Inner();
public class Outter2 {
private static int test = 10;
private String msg = "外部类的成员变量";
// 静态内部类,就是一个普通类,只是把他套在了Outter的里面而已。
public static class Inner{
private static int num = 100;
private String str = "内部类的成员成员变量";
public void fun(){
System.out.println(test);
System.out.println(new Outter2().msg);
}
}
public void test(){
Inner inner = new Inner();
}
public static void main(String[] args) {
Inner inner = new Inner();
inner.fun();
}
}
注意事项
- 静态内部类就是一个普通的类,只是套在了一个类的内部而已
- 为何成员方法和静态方法都能创建成员内部类对象?
答:类比类中静态变量,没有类的对象就能使用,类的静态方法可以调用,成员方法也可以。
- 静态内部类能否拥有成员变量?能否访问外部类的成员变量?
答:成员内部类可以访问外部类的成员域和静态域,但是不能拥有静态域。
静态内部类可以拥有成员域和静态域,但是不能直接访问外部类的成员域,需要外部类的对象才能访问,静态域没有对象也可以访问。
3.方法内部类
定义
直接定义在方法内部的类,不予许使用任何访问修饰符,对外部完全隐藏(除了这个方法,这个类就没有了)
public class Outter3 {
public void fun(int num){
//方法内部类,不能出现任何访问修饰符和static
class Inner{
}
}
}
注意事项
- 方法内部类无法定义
static
域,于此之外和成员内部类的用法基本相同。 - 方法内部类中若使用了方法的形参,该形参为隐式的
final
声明(这是JDK8之后的语法,JDK8之前,方法内部类若使用了形参,形参必须使用final
声明)
未使用形参
public class Outter3 {
public void fun(int num){
//此时方法内部类没有使用形参
class Inner{
}
num++;
System.out.println(num);
}
public static void main(String[] args) {
Outter3 outter3 = new Outter3();
outter3.fun(10);
}
}
输出结果
11
使用形参
4.匿名内部类
定义
匿名内部类是方法内部类的特殊版本,直接不写类名称(Lambda表达式的前身,函数式编程)
注意事项
- 绝大部分用在方法传参
- 匿名内部类遵从方法内部类的所有要求,匿名内部类默认会继承一个类或实现一个接口,普通类和抽象类都可以,一般是继承抽象类或实现接口。
使用接口传参引用
public class Outter4 {
public static void fun(IMessage msg){
msg.printMsg();
}
public static void main(String[] args) {
IMessage iMessage = new IMessageImpl();
fun(iMessage);
}
}
interface IMessage{
void printMsg();
}
class IMessageImpl implements IMessage{
@Override
public void printMsg() {
System.out.println("普通用法");
}
}
使用匿名内部类
public class Outter4 {
public static void fun(IMessage msg){
msg.printMsg();
}
public static void main(String[] args) {
fun(new IMessage(){
@Override
public void printMsg() {
System.out.println("匿名内部类的用法");
}
});
}
}
interface IMessage{
void printMsg();
}
编译后的匿名内部类
7️⃣补充说明
1.toString方法
public class Main {
private String name = "张三";
private int age = 10;
public void show(){
System.out.println("myname:" + name + ",myage:" + age);
}
public static void main(String[] args) {
Main main = new Main();
main.show();
System.out.println(main);
}
}
//输出结果:
myname:张三,myage:10
Main@1540e19d
可以使用 toString 这样的方法来将对象自动转成字符串.
public class Main {
private String name = "张三";
private int age = 10;
public void show(){
System.out.println("myname:" + name + ",myage:" + age);
}
public String toString(){
return "名字:" + name + ",年龄:" + age;
}
public static void main(String[] args) {
Main main = new Main();
main.show();
System.out.println(main);
}
}
//输出结果:
myname:张三,myage:10
名字:张三,年龄:10
注意事项
toString
方法会在println
的时候被自动调用.- 将对象转成字符串这样的操作我们称为 序列化.
toString
是Object
类提供的方法, 我们自己创建的Person
类默认继承自Object
类, 可以重写toString
方法实现我们自己版本的转换字符串方法.- @Override 在 Java 中称为 “注解”, 此处的 @Override 表示下面实现的 toString 方法是重写了父类的方法.
2.匿名对象
匿名只是表示没有名字的对象.
- 没有引用的对象称为匿名对象.
- 匿名对象只能在创建对象时使用.
- 如果一个对象只是用一次, 后面不需要用了, 可以考虑使用匿名对象.
class Person {
private String name;
private int age;
public Person(String name,int age) {
this.age = age;
this.name = name;
}
public void show() {
System.out.println("name:"+name+" " + "age:"+age);
}
}
public class Main {
public static void main(String[] args) {
new Person("caocao",19).show();//通过匿名对象调用方法
}
}
// 执行结果
name:caocao age:19