public static void main(String[] args) { //这里的a存放的是具体的某个值 int a = 10; //创建一个变量指代我们刚刚创建好的对象,变量的类型就是对应的类名 //这里的p存放的是对象的引用,而不是本体,我们可以通过对象的引用来间接操作对象 Person p = new Person();}
至于为什么对象类型的变量存放的是对象的引用,比如:
public static void main(String[] args) { Person p1 = new Person(); Person p2 = p1;}
public static void main(String[] args) { Person p1 = new Person(); Person p2 = p1; System.out.println(p1 == p2); //使用 == 可以判断两个变量引用的是不是同一个对象}
但是如果我们像这样去编写:
public static void main(String[] args) { Person p1 = new Person(); //这两个变量分别引用的是不同的两个对象 Person p2 = new Person(); System.out.println(p1 == p2); //如果两个变量存放的是不同对象的引用,那么肯定就是不一样的了}
public static void main(String[] args) { Person p = new Person(); p.name = "小明"; //要访问对象的属性,我们需要使用 . 运算符 System.out.println(p.name); //直接打印对象的名字,就是我们刚刚修改好的结果了}
public static void main(String[] args) { Person p = new Person(); System.out.println("name = "+p.name); System.out.println("age = "+p.age); System.out.println("sex = "+p.sex);}
int sum(int a, int b){ int c = a + b; return c; //return后面紧跟需要返回的结果,这样就可以将计算结果丢出去了 //带返回值的方法,是一定要有一个返回结果的!否则无法通过编译!}
我们来测试一下吧:
public static void main(String[] args) { Person p = new Person(); p.name = "小明"; p.age = 18; int result = p.sum(10, 20); //现在我们要让这个对象帮我们计算10 + 20的结果 System.out.println(result); //成功得到30,实际上这里的println也是在调用方法进行打印操作}
void swap(int a, int b){ //这个函数的目的很明显,就是为了交换a和b的值 int tmp = a; a = b; b = a;}
那么我们来测试一下:
public static void main(String[] args) { Person p = new Person(); int a = 5, b = 9; //外面也叫a和b p.swap(a, b); System.out.println("a = "+a+", b = "+b); //最后的结果会变成什么样子呢?}
public static void main(String[] args) { Person p = new Person(); p.name = "小明"; //先在外面修改一次 p.modify(p); //调用方法再修改一次 System.out.println(p.name); //请问最后name会是什么?}
public class Person { String name; int age; String sex; Person(){ //构造方法不需要指定返回值,并且方法名称与类名相同 name = "小明"; //构造方法会在对象创建时执行,我们可以将各种需要初始化的操作都在这里进行处理 age = 18; sex = "男"; }}
构造方法会在new的时候自动执行:
public static void main(String[] args) { Person p = new Person(); //这里的new Person()其实就是在调用无参构造方法 System.out.println(p.name);}
当然,我们也可以为构造方法设定参数:
public class Person { String name; int age; String sex; Person(String name, int age, String sex){ //跟普通方法是一样的 this.name = name; this.age = age; this.sex = sex; }}
public class Person { String name; int age; String sex; static String info; //这里我们定义一个info静态变量}
我们来测试一下:
public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person(); p1.info = "杰哥你干嘛"; System.out.println(p2.info); //可以看到,由于静态属性是属于类的,因此无论通过什么方式改变,都改变的是同一个目标}
所以说一般情况下,我们并不会通过一个具体的对象去修改和使用静态属性,而是通过这个类去使用:
public static void main(String[] args) { Person.info = "让我看看"; System.out.println(Person.info);}
package com.test;import com.test.entity.Person; //使用import关键字导入其他包中的类public class Main { public static void main(String[] args) { Person person = new Person(); //只有导入之后才可以使用,否则编译器不知道这个类从哪来的 }}
package java.lang;import java.io.*;import java.lang.reflect.Executable;import java.lang.annotation.Annotation;import java.security.AccessControlContext;import java.util.Properties;import java.util.PropertyPermission;import java.util.StringTokenizer;import java.util.Map;import java.security.AccessController;import java.security.PrivilegedAction;import java.security.AllPermission;import java.nio.channels.Channel;import java.nio.channels.spi.SelectorProvider;import sun.nio.ch.Interruptible;import sun.reflect.CallerSensitive;import sun.reflect.Reflection;import sun.security.util.SecurityConstants;import sun.reflect.annotation.AnnotationType;import jdk.internal.util.StaticProperty;/** * The <code>System</code> class contains several useful class fields * and methods. It cannot be instantiated. * * <p>Among the facilities provided by the <code>System</code> class * are standard input, standard output, and error output streams; * access to externally defined properties and environment * variables; a means of loading files and libraries; and a utility * method for quickly copying a portion of an array. * * @author unascribed * @since JDK1.0 */public final class System { ...}
public class Main { public static void main(java.lang.String[] args) { //主方法的String参数是java.lang包下的,我们需要明确指定一下,只需要在类名前面添加包名就行了 com.test.entity.String string = new com.test.entity.String(); }}
public void setName(String name) { if(name.contains("小")) return; this.name = name;}
我们甚至还可以将构造方法改成私有的,需要通过我们的内部的方式来构造对象:
public class Person { private String name; private int age; private String sex; private Person(){} //不允许外部使用new关键字创建对象 public static Person getInstance() { //而是需要使用我们的独特方法来生成对象并返回 return new Person(); }}
通过这种方式,我们可以实现单例模式:
public class Test { private static Test instance; private Test(){} public static Test getInstance() { if(instance == null) instance = new Test(); return instance; }}
public class Student extends Person{ public Student(String name, int age, String sex) { //因为学生职业已经确定,所以说学生直接填写就可以了 super(name, age, sex, "学生"); //使用super代表父类,父类的构造方法就是super() } public void study(){ System.out.println("我的名字是 "+name+",我在学习!"); }}
public class Worker extends Person{ public Worker(String name, int age, String sex) { super(name, age, sex, "工人"); //父类构造调用必须在最前面 System.out.println("工人构造成功!"); //注意,在调用父类构造方法之前,不允许执行任何代码,只能在之后执行 }}
我们在使用子类时,可以将其当做父类来使用:
public static void main(String[] args) { Person person = new Student("小明", 18, "男"); //这里使用父类类型的变量,去引用一个子类对象(向上转型) person.hello(); //父类对象的引用相当于当做父类来使用,只能访问父类对象的内容}
public static void main(String[] args) { Person person = new Student("小明", 18, "男"); Student student = (Student) person; //使用强制类型转换(向下转型) student.study();}
public static void main(String[] args) { Person person = new Worker("小明", 18, "男"); //实际创建的是Work类型的对象 Student student = (Student) person; student.study();}
image-20220921160309835
此时直接出现了类型转换异常,因为本身不是这个类型,强转也没用。
那么如果我们想要判断一下某个变量所引用的对象到底是什么类,那么该怎么办呢?
public static void main(String[] args) { Person person = new Student("小明", 18, "男"); if(person instanceof Student) { //我们可以使用instanceof关键字来对类型进行判断 System.out.println("对象是 Student 类型的"); } if(person instanceof Person) { System.out.println("对象是 Person 类型的"); }}
public static void main(String[] args) { Person p1 = new Student("小明", 18, "男"); Person p2 = new Student("小明", 18, "男"); System.out.println(p1.equals(p2));}
public class Person{ ... @Override //重写方法可以添加 @Override 注解,有关注解我们会在最后一章进行介绍,这个注解默认情况下可以省略 public boolean equals(Object obj) { //重写方法要求与父类的定义完全一致 if(obj == null) return false; //如果传入的对象为null,那肯定不相等 if(obj instanceof Person) { //只有是当前类型的对象,才能进行比较,要是都不是这个类型还比什么 Person person = (Person) obj; //先转换为当前类型,接着我们对三个属性挨个进行比较 return this.name.equals(person.name) && //字符串内容的比较,不能使用==,必须使用equals方法 this.age == person.age && //基本类型的比较跟之前一样,直接== this.sex.equals(person.sex); } return false; }}
在重写Object提供的equals方法之后,就会按照我们的方式进行判断了:
public static void main(String[] args) { Person p1 = new Student("小明", 18, "男"); Person p2 = new Student("小明", 18, "男"); System.out.println(p1.equals(p2)); //此时由于三个属性完全一致,所以说判断结果为真,即使是两个不同的对象}
public class Person { ... public void exam(){ System.out.println("我是考试方法"); } ...}
public class Student extends Person{ ... @Override public void exam() { System.out.println("我是学生,我就是小镇做题家,拿个 A 轻轻松松"); }}
public class Worker extends Person{ ... @Override public void exam() { System.out.println("我是工人,做题我并不擅长,只能得到 D"); }}
这样,不同的子类,对于同一个方法会产生不同的结果:
public static void main(String[] args) { Person person = new Student("小明", 18, "男"); person.exam(); person = new Worker("小强", 18, "男"); person.exam();}
public abstract class Student extends Person{ //如果抽象类的子类也是抽象类,那么可以不用实现父类中的抽象方法 public Student(String name, int age, String sex) { super(name, age, sex, "学生"); } @Override //抽象类中并不是只能有抽象方法,抽象类中也可以有正常方法的实现 public void exam() { System.out.println("我是学生,我就是小镇做题家,拿个 A 轻轻松松"); }}
public interface Study { //使用interface表示这是一个接口 void study(); //接口中只能定义访问权限为public抽象方法,其中public和abstract关键字可以省略}
我们可以让类实现这个接口:
public class Student extends Person implements Study { //使用implements关键字来实现接口 public Student(String name, int age, String sex) { super(name, age, sex, "学生"); } @Override public void study() { //实现接口时,同样需要将接口中所有的抽象方法全部实现 System.out.println("我会学习!"); }}
public class Teacher extends Person implements Study { protected Teacher(String name, int age, String sex) { super(name, age, sex, "教师"); } @Override public void study() { System.out.println("我会加倍学习!"); }}
接口不同于继承,接口可以同时实现多个:
public class Student extends Person implements Study, A, B, C { //多个接口的实现使用逗号隔开}
public interface Study { public static final int a = 10; //接口中定义的静态变量只能是public static final的 public static void test(){ //接口中定义的静态方法也只能是public的 System.out.println("我是静态方法"); } void study();}
跟普通的类一样,我们可以直接通过接口名.的方式使用静态内容:
public static void main(String[] args) { System.out.println(Study.a); Study.test();}
public class Student extends Person implements Study { private String status; //状态,可以是跑步、学习、睡觉这三个之中的其中一种 public String getStatus() { return status; } public void setStatus(String status) { this.status = status; }}
//这里使用javap命令对class文件进行反编译得到 Compiled from "Status.java"public final class com.test.Status extends java.lang.Enum<com.test.Status> { public static final com.test.Status RUNNING; public static final com.test.Status STUDY; public static final com.test.Status SLEEP; public static com.test.Status[] values(); public static com.test.Status valueOf(java.lang.String); static {};}
既然枚举类型是普通的类,那么我们也可以给枚举类型添加独有的成员方法:
public enum Status { RUNNING("睡觉"), STUDY("学习"), SLEEP("睡觉"); //无参构造方法被覆盖,创建枚举需要添加参数(本质就是调用的构造方法) private final String name; //枚举的成员变量 Status(String name){ //覆盖原有构造方法(默认private,只能内部使用!) this.name = name; } public String getName() { //获取封装的成员变量 return name; }}
这样,枚举就可以按照我们想要的中文名称打印了:
public static void main(String[] args) { Student student = new Student("小明", 18, "男"); student.setStatus(Status.RUNNING); System.out.println(student.getStatus().getName());}