作者:信仰壳
链接:https://zhuanlan.zhihu.com/p/496036029
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在说类和对象之前,先来讲一讲面向对象是什么?
什么是面向对象?
在此之前,其实我们都知道了Java是一门纯面向对象的语言。所以在Java程序员的眼中,一切都是对象。
面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。使用面向对象的思想来涉及程序,更加符合人们对事物的认知,对于大型程序的设计,扩展以及维护都是十分友好的。
类
类的定义
在了解完面向对象是什么之后,接下来就来讲讲类。 在Java中定义一个类时时需要使用class关键字的,类的定义格式如下:
class ClassName{
field; //字段/属性/成员变量
method; //行为/成员方法
}
class是定义类的关键字,ClassName是类的名字,{}中是类的主体。 其中,类中包含的内容是类的成员: 成员变量(也叫字段或者成员属性)主要是用来描述类的; 成员方法(也叫行为)主要是用来说明类具有哪些功能。
下面就以定义一个类为例:
class Person{
public String name;
public int age;
public String sex;
public void sleep(){
System.out.println(name+"正在睡觉");
}
public void eat(){
System.out.println(name+"正在吃饭");
}
}
注意:类名应该使用大驼峰来定义!!!
类的实例化
在之前写的代码中,我们一直会使用到int、char、double等这些类型,当然,这些都是Java中自带的内置类型。而今天介绍的类是一种新的类型,是用户定义的一个新的类型,有了这些自定义类型之后,就可以使用这些类型来定义实例(对象),用这些类类型来创建对象的过程,就叫做类的实例化(简单地说就是创建一个对象)。在Java中是采用new关键字,配合类名来实例化对象。
上面定义的类进行实例化可以是:
Person person=new Person();
当然,同一个类可以创建多个对象,例如:
Person person1=new Person();
Person person2=new Person();
Person person3=new Person();
然后使用 **.**
就可以访问对象中的属性和方法,例如下面这样(完整代码):
class Person{
public String name;
public int age;
public String sex;
public void sleep(){
System.out.println(name+"正在睡觉");
}
public void eat(){
System.out.println(name+"正在吃饭");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1=new Person();
Person person2=new Person();
Person person3=new Person();
person1.name="张三";
person1.eat();
person2.name="李四";
person2.sleep();
person3.name="王五";
person3.eat();
}
}
其实上面的这段代码使用到了对象的初始化,那么什么是对象的初始化呢? 请继续看下一章,会有你想要的答案。
对象的初始化及this引用
对象的初始化
常规的对象初始化有两种,可分为:就地初始化和默认初始化。
就地初始化
就地初始化就是最简单的,在声明成员变量的时候,就直接给出初始值。
class Dog{
public String name="小黑";
public int age=6;
public String sex="男";
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
优点:
- 这样可以设置一个固定参数,比如想要实例化的这些狗都是“男”的,但是在对象初始化的时候忘记了赋值操作,这时候就不会对整个工程造成影响;就算有一只是“女”的,也只需要在初始化的时候区别对待它(将它的sex重新赋值),其他的“男”狗就不需要进行对应操作,这就有点像数据库三种的设置默认参数了。
- 如果是整个工程只有一个对象且都不用修改成员变量的值的时候,是可以直接这样子写的,不用后面再通过其他方式对这个对象进行初始化(但是最好还是使用默认初始化,除非能确定后面再也不会再对这段代码进行修改等操作)。
缺点:
- 就地初始化的缺点就很明显了,就是代码编译完成后,编译器会将所有给成员初始化的这些语句添加到各个构造函数中。
综上:我的建议还是尽量少使用就地初始化。
默认初始化
相对于就地初始化,默认初始化就会更加实用一些。
我们都知道,如果我们不对成员变量进行就地初始化,那么这时候,就JVM会初始化之前所分配的空间,这些成员也会自动设置好初始值,比如常见的:int类型对应0;String类型对应null;double类型对应0.0;boolean对应false。这时候,就可以对这些成员进行默认初始化。 目前,常用的默认初始化有三种: **.**
访问初始化、set方法初始化、构造方法初始化
**.**
访问属性进行初始化
class Person{
public String name;
public int age;
public String sex;
public void sleep(){
System.out.println(name+"正在睡觉");
}
public void eat(){
System.out.println(name+"正在吃饭");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1=new Person();
Person person2=new Person();
Person person3=new Person();
person1.name="张三";
person1.eat();
person2.name="李四";
person2.sleep();
person3.name="王五";
person3.eat();
}
}
就像类似的,通过引用名 . 成员变量,就可以对其进行初始化。
set方法对属性进行初始化
class Dog{
public String name;
public int age;
public String sex;
public void setDog(String n, int a, String s) {
name = n;
age = a;
sex = s;
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
set方法,顾名思义,就是用户自己写一个方法,然后通过调用这个方法来实现对属性的初始化。
构造方法初始化
class Dog{
public String name="小黑";
public int age=6;
public String sex="男";
public Dog(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
那么,这里的构造方法又是什么呢?请继续往下看。
构造方法
通过对象的初始化,我们就引出了所谓的构造方法。构造方法在整个类和对象中起到了 非常重要的作用!!!也是这整篇文章 最重要的部分之一。既可以承接上面的对象初始化,又可以引出下面的_this_引用, 非常重要!!!
概念
构造方法(也称为构造器)是一个特殊的成员方法, 名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
public Dog(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
注意:构造方法的作用就是对对象中的对象进行初始化,并不负责给对象开辟空间。
构造方法的7歌特性
- 名字必须与类名相同
- 没有返回值类型,设置为void类型也是不行的
- 创建对象时由编译器自动调用,并且在对象中的生命周期内只调用一次
- 当写一个类之后,没有写构造方法的时候,编译器会帮我们默认生成一个不带参数的构造方法(也就是说一个类就算没写构造函数,也至少有一个构造函数)
class Dog{
public String name;
public int age;
public String sex;
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
上面这段代码实际上就相当于下面这段代码(加入一个不带参数的构造方法):
class Dog{
public String name;
public int age;
public String sex;
public Dog(){
;
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
- 当在类中写了任何一个构造方法之后,编译器将不再为我们提供一个不带参数的构造方法
class Dog{
public String name;
public int age;
public String sex;
public Dog(String name) {
this.name = name;
}
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
对于这种自己写了构造方法之后,就必须在实例化对象的时候传入参数(正如上面这个例子一样),如果没有传入参数的话则将会报错(因为此时编译器不再提供一个不带参数的构造方法)。
- 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
class Dog{
public String name;
public int age;
public String sex;
public Dog(String name) {
this.name = name;
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
此时的两个构造方法名字相同,但是参数列表不同,因此就会构成重载。这个时候就会通过实例化对象的时候传入多少个对象来判别使用哪一个构造方法。
- 在构造方法中,可以通过_this_调用其他构造方法(这点同时也是后面_this_引用的内容)
class Dog{
public String name;
public int ag;
public String sex;
public Dog(){
this("小黄",4,"男");
}
public Dog(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
关于_this_的内容在后面会详细讲到,请继续往下看。
- 绝大多数情况下使用public来修饰,特殊场景下会被private修饰(后续讲单例模式时会遇到)。
什么是this引用?
引言: 在Java中,Java编译器会给每一个“成员方法”增加一个隐藏的引用类型参数,该引用参数指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用区访问。只不过所有的操作对用户时透明的,即用户不需要通过传递,编译器就可以自动完成。
上面的引言中,可以提取分离出下面几点:
- _this_引用时编译器自动添加的,用户在实现代码时一般不需要显示给出,正如下面的一段代码:
class Person{
public String name;
public int age;
public String sex;
public void setPerson(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
public void eat(){
System.out.println(name+"正在吃饭");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1=new Person();
person1.setPerson("张三",23,"男");
Person person2=new Person();
person2.setPerson("李四",13,"男");
Person person3=new Person();
person3.setPerson("王五",33,"女");
person1.eat();
person2.sleep();
person3.eat();
}
}
由上面引言可知,其实这段中的成员方法都是有一个隐藏参数的,且都是位于第一个参数,补全之后的代码应该时这样子:
class Person{
public String name;
public int age;
public String sex;
public void setPerson(Person this, String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void sleep(Person this){
System.out.println(name+"正在睡觉");
}
public void eat(Person this){
System.out.println(name+"正在吃饭");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1=new Person();
person1.setPerson("张三",23,"男");
Person person2=new Person();
person2.setPerson("李四",13,"男");
Person person3=new Person();
person3.setPerson("王五",33,"女");
person1.eat();
person2.sleep();
person3.eat();
}
}
在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,_this_负责来接收。
- _this_引用是是调用成员方法的对象
- _this_只能在“成员方法”中使用
- 在成员方法中,_this_只能引用当前对象,不能再引用其他对象
为什么要使用this引用?
总结来说,就是如果在构造方法中,形参名与成员变量名相同时(也就是如果把),可能就会写出下面这样的一段代码:
public void setPerson(Person this, String name, int age, String sex) {
name = name;
age = age;
sex = sex;
}
写出这样的代码之后,编译器就会直接报错,因为这样子编译器不能判断到底哪个对应的是形参,哪个对应的是成员变量。
使用this的三种情况
this访问成员变量
语法格式:
this.data; //访问成员变量
上面的例子中使用的_this_基本都是是这种情况(见上)。
this访问成员方法
语法格式:
this.fuc(); //访问成员方法
具体代码:
class Person{
public String name;
public int age;
public String sex;
public void setPerson(Person this, String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void sleep(Person this){
System.out.println(name+"正在睡觉");
this.eat();
}
public void eat(Person this){
System.out.println(name+"正在吃饭");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1=new Person();
person1.setPerson("张三",23,"男");
Person person2=new Person();
person2.setPerson("李四",13,"男");
Person person3=new Person();
person3.setPerson("王五",33,"女");
person1.eat();
person2.sleep();
person3.eat();
}
}
在sleep方法中通过this来调用eat方法。
this调用构造方法
语法格式:
this(); //调用构造方法
具体代码(与上面说到的在构造方法中,可以通过_this_调用其他构造方法的代码例子类似):
class Person{
public String name;
public int age;
public String sex;
public Person(){
this(15);
System.out.println();
}
public Person(int age){
this.age=age;
}
public void setPerson(Person this, String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void sleep(Person this){
System.out.println(name+"正在睡觉");
this.eat();
}
public void eat(Person this){
System.out.println(name+"正在吃饭");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person1=new Person();
//person1.setPerson("张三",23,"男");
Person person2=new Person();
//person2.setPerson("李四",13,"男");
Person person3=new Person();
//person3.setPerson("王五",33,"女");
person1.eat();
person2.sleep();
person3.eat();
}
}
注意:
- 使用this调用构造方法的那一行(也就是
_this_(···)
),必须是构造方法中的第一条语句(也就是写在构造方法的第一行)。 - 不能形成环(也就是说不能在_this_调用的那个构造方法中再使用_this_调用回来这个方法)
封装
什么是封装?
封装就是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交换。
封装:在实现上采用private修饰成员变量和成员方法,这样在类外就不能直接直接访问了。对外隐藏实现细节,只提供公开方法,来操作你的这些封装好的成员变量和成员方法。实质上就是公开方法和私有属性的结合。
访问限定符
引言:在前面我们所写的所有代码基本都是使用public修饰的,因为其是公开的,到哪都是可以使用的,不用去思考任何访问权限之类的东西,拿起来就用。
类中成员访问权限
其实,Java中主要都是通过类和访问权限来实现封装的。类可以将数据以及封装数据的方法结合在一起,而访问权限用来控制方法或者字段能否直接在类外使用。对此,Java提供了四种访问限定符:
- private(私有):只能在当前的类中使用,会变得非常安全。
class Cat{
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
public class TestDemo2 {
public static void main(String[] args) {
Cat cat=new Cat("小白",13);
cat.sleep();
}
}
复习一下,我们知道默认初始化的方法有三种: **.**
访问初始化、set方法初始化、构造方法初始化。但是因为这里的Cat类中使用的是private来修饰成员变量,在本类外的任何地方都是不能够访问的,所以在main方法中就不能够再使用 **.**
访问来进行初始化了,只剩set方法初始化、构造方法初始化这两种不是直接访问的方法可行。
- 什么都不写(默认/包访问权限):只能在同一个包文件中使用。下面会讲到,请继续往下看。
- protected(继承权限):是介于包访问权限与公开访问权限(public)之间,主要是用在继承中的,后面文章介绍继承的时候会详细讲到。
- public(公开):哪里都能够访问。
总结:一般情况下成员变量设置为private,成员方法设置为public。但不是绝对的,根据实际情况决定!
类访问权限
当然,访问权限除了可以限定类中成员的可见性,也可以控制类的可见性。 但是,控制类可见性只有两种:公开访问权限(public)和包访问权限。 也就是说,如果定义一个类开始直接就写class,那么就是控制这个类只能在同一个包文件中可用;如果是public class开始,那么哪里都可以使用这个类。 这里还要再次强调的一点是:一个.java文件中只能有一个类是public修饰!!!一个java文件中只能有一个类是public修饰!!!一个java文件中只能有一个类是public修饰!!!
封装扩展——包
什么是包?
在面向对象的体系中, 为了更好的管理类,吧多个类收集在一起成为一组,于是,便提出了一个“ 软件包”的概念。
Java也是引入了包的概念,包是对象、接口等封装机制的体现,是一种对类或接口等很好的组织方法。 同时,包还有一个重要的作用:在同一个工程中允许存在相同名称否类,只要是处在不同否包中即可。
导入包中的类
Java中已经提供了很多现成的类,这些类我们都是可以直接拿来使用的。比如 Arrays类,我们就可以使用java.util.Arrays
来导入java.util
这个包中的Arrays
类。
如何导入包?
导入包主要有以下几种方法:
- 直接使用
import
语句来导入包。就像上面一样:import java.util.Arrays
就可以导入这个类。当然,也可以使用import java.util.*
来进行导入,这样写的特点是可以导入java.util
这个包中的所有类,这其中当然也就包含了Arrays
类。 - 可以使用
import static
导入包中的静态方法和字段。比如要导入Math这个类,我们可以这样写:
double ret=Math.sqrt(100);
System.out.println(ret);
这样写是不需要导入任何类的,也可以可以使用import static
来实现,示例如下:
import static java.lang.Math.*;
public class Main {
public static void main(String[] args) {
double ret=sqrt(100);
System.out.println(ret);
}
}
注意点
- 在上面第一点中比较建议使用显式地指定要导入的类名,就像这样
import java.util.Arrays
写全,否则可能会容易出现冲突等情况。 - 有
import static
这种导入包的方法,但是还是不太建议使用。 import
和C++中的#include
差别是很大的,C++必须使用#include
来引入其他文件内容,但是Java不需要,import
知识为了写代码的时候更方便,import
更类似于C++中的namespace
和using
。
自定义包
在Java提供的包中没有我们所需要的,那么这时候我们就可以自己自定义一个包,再进行调用使用,同时别人也可以这个自定义包。
基本规则
- 在文件的最上方加上一个package语句指定该代码在哪个包中
- 包名需要尽量指定成唯一的名字,通常会用公司的域名的颠倒形式
- 包名要和代码路径相匹配
- 如果一个类没有package语句,则该类被放到一个默认包中
包访问权限
上面已经有提到了包访问权限,这里做一个详细的介绍:
- 包访问权限对类或者是对成员方法,都是不用写像public或者private之类的。
- 包访问权限只能在同一个包中使用,如果不在同一个包,则可能会报错:不允许被其他类访问。
常见的包
java.lang
:系统常用基础类(String、Object)java.lang.reflect
:Java反射编程包java.net
:进行网络编程开发包java.sql
进行数据库开发的支持包java.util
:是Java提供的工具程序包(集合类等),非常重要!!!java.io
:I/O编程开发包
static成员
在此之前,我们在类中定义的成员变量基本都是实例变量,接下来,我来介绍静态变量。
在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的。
static修饰成员变量
static修饰成员变量,称为静态成员变量。 静态成员变量的特性:
- 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
- 既可以通过对象访问,也可以通过类名访问,但是一般都是使用类名访问。(通过对象访问是合法的,但不合理)
- 类变量存储在方法区中
- 生命周期伴随类的一生(也就是说随着类的加载而创建,随着类的卸载而销毁)
示例:
class Dog{
public static String name="小黑";
public static int age=6;
public static String sex="男";
}
public class Main {
public static void main(String[] args) {
System.out.println(Dog.name);
System.out.println(Dog.age);
System.out.println(Dog.sex);
}
}
static修饰成员方法
在Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般通过静态方法来访问的。(一般类中的数据成员都设置为private,而成员方法设置为public),示例如下:
class Dog{
private static String name="小黑";
private static int age=6;
private static String sex="男";
public static void sleep(){
System.out.println(name+"正在睡觉");
}
}
public class Main {
public static void main(String[] args) {
Dog.sleep();
}
}
静态方法的特性:
- 不属于某个具体对象,是类方法
- 可以通过对象调用,也可以直接通过类名.静态方法名(……)方式调用,但是一般都是使用类名.静态方法名进行调用(通过对象调用是合法但是不合理的)
- 静态方法没有隐藏的this引用参数,因此不能在静态方法中访问任何非静态成员变量或者非静态方法
总结
使用static修饰的不管是成员变量还是成员方法,只要是static修饰的,就不依赖对象,也就是不需要进行实例化。
代码块
代码块主要可以分成以下四种:普通代码块、实例代码块、静态代码块、同步代码块(后续文章中再详细讲)
普通代码块
定义在方法中的代码块称为普通代码块。
相对来说,使用的会比较少。用法是直接在方法中输入{ }
,并在其中间进行代码的编写。
实例代码块
定义在类中的代码块(不加修饰符)称为实例代码块。
注意:实例代码块一般用于初始化实例成员变量。实例代码块优先于构造方法执行,因为编译完成后,编译器会将实例代码块中的代码拷贝到每个构造方法第一条语句前。还有,实例代码块只有在创建对象是才会执行。
class Cat{
public String name;
public int age;
{
this.name="小黄";
}
public void sleep(){
System.out.println(this.name+"正在睡觉");
}
}
public class Main {
public static void main(String[] args) {
Cat cat=new Cat();
cat.sleep();
}
}
静态代码块
使用static定义的代码块称为静态代码块。
注意:
- 静态代码块一般用于初始化静态成员变量
- 静态代码块不管生成多少个对象,都只会执行一次
- 静态成员变量是类的属性,因此在JVM加载类时开辟空间并初始化的
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次合并,最终放在生成的<>方法中,该方法在类加载是调用,并且只调用一次
class Cat{
public String name;
public int age;
private static String sex;
static {
sex="男";
}
public void sleep(){
System.out.println(this.name+"正在睡觉");
}
}
总结
在编译器进行编译的时候,执行静态代码块会优先于实例代码块,实例代码块优先于构造方法。(十分重要!!!)
内部类
当一个事物的内部,还有一部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。在Java中,可以 将一个类定义在另一个类或者一个方法内部,前者称为内部类,后者称为外部类,内部类也是封装的一种体现。
内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件。 内部类可以分为以下几种:
- 实例内部类
- 静态内部类
- 局部内部类(定义在方法里面,基本上不会使用到,这里不做介绍)
- 匿名内部类(使用的最多,后续接口部分会详细讲到)
实例内部类
- 外部类中的任何成员都可以在实例内部类方法中直接访问
class Outclass{
public static int a;
private int b;
class Innerclass{
public void func(){
a=100;
}
}
}
- 实例内部类所处的成员与外部类成员位置相同,因此也受public、private等访问限定符的约束
- 在实例内部类方法中访问同名的成员时,优先访问自己的,如果想要访问外部类的同名成员,必须使用
外部类类名.this.同名成员
来进行访问
class Outclass{
public static int a;
private int b;
class Innerclass{
int b;
public void func(){
b=300;
System.out.println(b);
Outclass.this.b=800;
System.out.println(Outclass.this.b);
}
}
}
- 实例内部类对象必须在先有外部类对象的前提下才能够创建的
public class Main {
public static void main(String[] args) {
//写法一:
Outclass outclass=new Outclass();
Outclass.Innerclass innerclass=outclass.new Innerclass();
//写法二:
Outclass.Innerclass innerclass=new Outclass().new Innerclass();
innerclass.func();
}
}
- 实例内部类的非静态方法中包含了一个指向外部类对象的引用
- 外部类中是不能够直接访问内部类中的成员,如果要访问就必须先要创建内部类对象
静态内部类
- 创建内部类时,不需要先创建外部类对象
public class Main {
public static void main(String[] args) {
Outclass.Innerclass innerclass=new Outclass.Innerclass();
innerclass.func();
}
}
- 在内部类中,只能够访问外部类中的静态成员
class Outclass{
public static int a;
public int b;
static class Innerclass{
public void func(){
a=100;
System.out.println(a);
}
}
}
总结
在实际的开发过程中,相比于实例内部类,我们会更加频繁地使用到静态内部类,因为其在创建内部类时,不需要先创建外部类对象,使用起来会更加方便一些。
对象的打印
我们之前对对象的成员进行打印的时候,是可以使用get()方法来进行逐一打印的。但是除了这样,我们又有什么方法来默认打印对象呢?
我们如果是像下面代码一样进行对象打印,则会报错:
class Dog{
public String name;
public int age;
public String sex;
public Dog(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
}
public class Main {
public static void main(String[] args) {
Dog dog=new Dog("小黑",6,"男");
System.out.println(dog);
}
}
正确的方法是:重写toString方法,示例如下:
class Dog{
public String name;
public int age;
public String sex;
public Dog(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
public class Main {
public static void main(String[] args) {
Dog dog=new Dog("小黑",6,"男");
System.out.println(dog);
}
}