Java基础语法(上)

Java简介

  • Java是一门程序开发语言. 1991年由sun公司的James Gosling和他的团队一起开发了一个叫Oak的语言. 在1995年更名为Java. 一直沿用至今.

  • Java是目前全世界使用人数最多的编程语言. Toibe 排行榜第一. https://www.tiobe.com/tiobe-index/

  • 丰富的开源社区和非常多的开源框架.

  • 国内各大一线公司都在使用Java.

特点:

  • 简单: 针对C++简化了很多

Java相关概念:

  • JVM(Java Virtual Machine): java虚拟机, 用来模拟统一的硬件平台环境, 供java程序运行的 一个软件

  • JRE(Java Runtime Environment): java运行时环境. 里面包含了JVM和一些java运行时的类库

  • JDK(Java Development Kit): java开发包. 包含JRE和java开发工具包.

第一个Java程序

  • 首先, 我们在c盘下创建一个文件夹. 叫test. 然后在test文件夹中.创建一个文本文档. 起名为first.java

  • 写入以下代码

public class first{
     public static void main(String[] args){
         System.out.println("你好 终于写了一个程序了.");
     }
 }
  • 使用命令javac对我们的程序进行编译

  • 使用命令javac对我们的程序进行编译

注意: javac命令后面跟的是文件名

java命令后面跟的是类名. 也就是我们代码中public class 后面的那个名字

接下来. 我们对程序进行详解:

public class first{    ------>  这一行表示创建一个类. 类是java程序运行的最小单元. 必须要有类
    public static void main(String[] args){    ----->    java程序的入口. 一切从这里开始执行
        System.out.println("你好 终于写了一个程序了.");  ---> 让程序打印一句话, 内容是引号中的内容
    }  ---> 对应程序入口的结束
} --> 对应类的结束

Java中的注释

一. 单行注释

// 单行注释. 这一行内容会被jvm运行的时候过滤掉. 

二. 多行注释

/*
 多行注释
 一次注释多行代码
 */

三. 文档注释

/**
  * 文档注释
  * 后期可以使用javadoc命令生成api文档
  */

常用的是单行注释 // 和 文档注释 /*** /

一般在如下情况可以添加代码注释:

  • 核心的解决方案
  • 比较难懂的业务逻辑
  • 记录代码的存在经历

变量

java中的变量:

数据类型 变量名 = 值;

本节, 我们只介绍一些最最常用的基本数据类型. 后面的进阶我们会进一步详细的介绍数据类型.

  1. int

    int主要表示的就是我们生活中使用的整数. 比如, 1, 10086等等.

    int类型可以执行的操作: 加减乘除等. + - /

     int a = 20;
     int b = 10;
    
     int c = a + b;
     System.out.println(“a + b = “ + c);
    
     int d = a - b;
     System.out.println(“a - b = “ + d);
    
     int e = a  b;
     System.out.println(“a  b = “ + e);
    
     int f = a / b;
     System.out.println(“a / b = “ + f);
  2. double

    double表示的是小数. 比如你去菜市场买个黄瓜花费了1.25元. 这就是小数. double类型的数据可以执行的操作和int基本相同. 不同的是. 计算结果一定是小数

     double a = 1.88;
     double b = 1.75;
    
     double c = a + b;
     System.out.println(“a + b = “ + c);
    
     double d = a - b;
     System.out.println(“a - b = “ + d);
    
     double e = a  b;
     System.out.println(“a * b = “ + e);
    
     double f = a / b;
     System.out.println(“a / b = “ + f);
  1. String 字符串

    String表示的是字符串. 主要是针对文字进行描述的. 这里要注意的是字符串(String) 的S必须大写

     String s = “近日, 三胖又要制造超大号窜天猴了”; // 双引号括起来的内容就是字符串
     System.out.println(“s = “ + s);

    字符串可以进行加法运算. 表示字符串的拼接.

     String s1 = “AAA”;
     String s2 = “BBB”;
     String s3 = s1 + s2;
     System.out.println(s3);
    
     String s4 = s3 + “, oh, yeah!”;
     System.out.println(s4);
  1. boolean 布尔

    boolean主要表示的是真或者假. 翻译过来就是 对和错. 真命题, 假命题

     boolean a = true;
     boolean b = false;
    
     System.out.println(a);
     System.out.println(b);
    
    
boolean c = 1 > 2;
boolean d = 2 > 1;
System.out.println(c);
System.out.println(d);

int e = 10; // = 表示赋值
System.out.println(e == 10); // 判断是否相等要用 ==
System.out.println(e != 10);
   ```java
   // boolean值是用来判断条件的

变量的简单应用

int meals = 100+200+120+40;  // 餐费
int traffic = 10 * 22;        // 交通费
int clothing = 200+400+150;  // 服装费
int all = meals + traffic + clothing;  //总和
System.out.println("一共花费:" + all); // 一共花费:1430

用户输入

在java中使用输入功能需要使用Scanner类来完成.

import java.util.Scanner;  // 使用Scanner必须要导入这个包. 关于导包后面会详解. 暂时先这么写

 public class TestIf {
     public static void main(String[] args) {

         // 准备一个Scanner对象
         Scanner sc = new Scanner(System.in);
     }
 }

具体操作

// 获取到用户输入的内容
 Scanner sc = new Scanner(System.in);

 // 你想要什么样的数据?
 // 常用的有
 // 获取到int类型的数据
 int i = sc.nextInt();
 System.out.println("i = " + i);

 // 获取到double类型的数据
 double d = sc.nextDouble();
 System.out.println("d = " + d);

 // 获取到字符串. 以回车为结尾
 String line = sc.nextLine();
 System.out.println("line = " + line);

应用: 计算这个月的零花钱

Scanner sc = new Scanner(System.in);

 // 提示
 System.out.println("请输入您的工资:");

 // 接收工资
 double salary = sc.nextDouble();

 // 显示工资
 System.out.println("工资是:" + salary);

 // 计算零花钱 
 double pin = salary * (1-0.95);

 // 显示零花钱
 System.out.println("零花钱:" + pin);

If语句

Scanner sc = new Scanner(System.in);
 System.out.println("请输入你一个月多少零花钱:");
 double pin = sc.nextDouble();
 if(pin > 500){
     System.out.println("洗脚城走一波");
 } else {
     System.out.println("捡一块砖头自己回家蹭");
 }
 ​
 System.out.println("happy");
// 输入考试分数
 Scanner sc = new Scanner(System.in);
 int score = sc.nextInt();
 if(score >= 100){
     System.out.println("优秀");
 } else if (score >= 80 ){
     System.out.println("良好");
 } else if (score >= 70 ){
     System.out.println("中等");
 } else if (score >= 60 ){
     System.out.println("及格");
 } else {
     System.out.println("不及格");
 }

While语句

System.out.println("1. 登录");
 System.out.println("2. 找个怪多的地方");

 int i = 0;
 while(i < 5){
     System.out.println("3. F1~ 来自东方的神秘力量.");
     System.out.println("4. 换个地方继续.");
     i = i + 1; 
 }

 System.out.println("5. 没意思, 走了~");

方法

  • 写一个试试一个能从1数到100的方法, 并调用
public class TestMethod {

    public static void count100(){
        for(int i = 1; i <= 100; i++){
            System.out.println(i);
        }
    }
    public static void main(String[] args) {
        count100();
    }
}
  • 方法的重载 (方法的名字相同, 参数的个数或类型不同)
public class TestMethod {

     public static int add(int a, int b){
         System.out.println("两个int");
         return a + b;
     }

     public static long add(int a, long b){
         System.out.println("一个int, 一个long");
         return a + b;
     }

     public static double add(int a, double b){
         System.out.println("一个int, 一个double");
         return a + b;
     }

     public static void main(String[] args) {
         System.out.println(add(10, 20));
         System.out.println(add(10, 20L));
         System.out.println(add(10, 1.25));
     }
 }

面向对象

  • 用面向对象的思维来完成植物大战僵尸
public class Plant {
     String name;
     int attack;
     int hp;

     public Plant(String name, int attack, int hp){
         this.name = name;
         this.attack = attack;
         this.hp = hp;
     }

     public void fight(Zombie zombie){
         System.out.println(this.name + "正在攻击:" + zombie.name);
         zombie.hp -= this.attack;
         System.out.println(zombie.name+"还剩下"+zombie.hp+"血量");
     }
 }
public class Zombie {
     String name;
     int attack;
     int hp;

     public Zombie(String name, int attack, int hp){
         this.name = name;
         this.attack = attack;
         this.hp = hp;
     }

     public void fight(Plant plant){
         System.out.println(this.name + "正在攻击:" + plant.name);
         plant.hp -= this.attack;
         System.out.println(plant.name+"还剩下"+plant.hp+"血量");
     }
 }
 public class Client {
     public static void main(String[] args) {
         Plant p1 = new Plant("豌豆荚", 10, 100);
         Zombie zb = new Zombie("铁通僵尸", 20, 300);

         p1.fight(zb);
         p1.fight(zb);
         p1.fight(zb);
         p1.fight(zb);
         p1.fight(zb);

         zb.fight(p1);
     }
 }

静态变量 (被static修饰的变量会被所有的对象共享, 并且, 在内存里只会保留一份)

 public class Person{
     String name;
     String address;
     static String country = "大清";
     public Person(String name, String address){
         this.name = name;
         this.address = address;
     }
     public static void main(String[] args){
         Person p1 = new Person("李大猛", "北京八大胡同");
         Person p2 = new Person("花花", "北京朝阳");
         p1.country = "民国";
         System.out.println(p1.country);
         System.out.println(p2.country); // 民国
     }
 }

静态方法

 public class Person{
     public static void huo(){
         System.out.println("是人都想火");
     }
     public static void main(String[] args){
         huo(); // 由于是自己类. 可以直接访问静态方法
         Person.huo();// 最好呢, 还是用类名去访问. 
     }
 }

在创建对象的时候, 首先加载的是静态的内容, 然后才是非静态内容.

在静态方法里, 不能使用this.

也不能直接调用自己类中的其他成员(方法, 变量). 但是反过来是可以的.

重点: 静态的东西不属于对象, 直接属于类, 优先于对象产生. 使用的时候使用类名去访问.

访问权限

释义 自己类 自己包 其他包
public 公共的 OK OK OK
default 默认的 OK OK NO
private 私有的 OK NO NO
package com.dylan.bao;

 public class Person {
     String def = "def"; // 默认啥都不写就是包访问权限
     public String pub = "pub"; // 公共的
     private String pri = "pri"; // 自己的

     public static void main(String[] args) {
         Person p = new Person();
         // 自己类里,都没问题

         System.out.println(p.def);
         System.out.println(p.pub);
         System.out.println(p.pri);

     }
 }

自己包里的其他类试试

package com.dylan.bao;

public class TestPackagePerson {

     public static void main(String[] args) {
         Person p = new Person();
 //      自己包里. private不行了

         System.out.println(p.def);
         System.out.println(p.pub);
 //      System.out.println(p.pri); // 报错了
     }
 }

一般情况, 我们很少用包访问权限. 这种权限并不舒服. 说白了. 你家里的东西要么是都能让人看的, 要么就是自己用的. 很少会专门准备一些东西给你的邻居用的. 程序也一样. 很少会用默认的访问权限.

getter and setter

 package com.xyq.bao;

 public class Person {
     private String name;
     private int age;
     // 设置名字
     public void setName(String name){
         this.name = name;
     }
     // 获取名字
     public String getName() {
         return name;
     }

     // 获取年龄
     public int getAge() {
         return age;
     }
     // 设置年龄
     public void setAge(int age){
         if(age < 0){
             this.age = 0;
         } else {
             this.age = age;
         }
     }

     public void chi(){
         System.out.println(this.name+"在吃东西");
     }
 }

IDEA软件中, 快捷键: 空白处, 右键-> generate -> getter and setter -> ctrl + A -> OK ! enjoy~

继承

写个妖怪

package com.dylan.bao;

 public class YaoGuai {
     private void paSi(){
         System.out.println("妖怪都怕死");
     }

     public void chiRen(){
         System.out.println("妖怪喜欢吃人");
     }
 }

写个黑熊

package com.dylan.bao;

 public class HeiXiong extends YaoGuai {

 }

测试一下

package com.dylan.bao;

 public class TestExtends {

     public static void main(String[] args) {
         HeiXiong hx = new HeiXiong();
         hx.chiRen();
 //        hx.paSi(); //报错, 找不到怕死
     }
 }

所以从另一个角度讲, 子类其实是对父类进行了扩展. 在父类已经给了一些方法和属性的基础上再加一点儿新功能. 可以节省很多的代码.

注意: java只能单继承. 说白了. 每个儿子只能有一个爹

多态

package com.dylan.bao;

 public class Animal{
     public void eat(){
         System.out.println("动物吃东西");
     }
 }
package com.dylan.bao;

 public class Dog extends Animal{
     public void eat(){
         System.out.println("狗吃骨头");
     }
 }
package com.dylan.bao;

 public class Cat extends Animal{
     public void eat(){
         System.out.println("猫吃鱼");
     }
 }

把子类的对象赋值给父类的变量. 这个在java里被称为向上转型.

向上转型的优点: 自动转型. 把不相关的东西转化成相同的数据类型. 猫, 狗, 鹦鹉都当成动物来看~~

package com.dylan.bao;

 public class Person{
     public void feed(Animal ani){
         ani.eat(); 
     }
 }
package com.dylan.bao;

 public class Test {
     public static void main(String[] args) {
         Person p = new Person();
         Animal c = new Cat();
         Animal d = new Dog();
         p.feed(c);
         f.feed(d);
     }
 }

多态的三个要素:

  • 要有继承关系

  • 要重写

  • 要向上转型

优点: 让程序有超强的可扩展性.

如果你想把父类的变量转化回子类的类型. 就必须要强转

(转化之后的类型) 变量

 public static void main(String[] args){     
     Animal ani = new Cat(); //向上转型     
     Cat c = (Cat) ani;//必须要强转 
 }

final关键字

  1. 被final修饰的变量: 不可以被重新赋值, 又被称为常量.

  2. 被final修饰的方法: 不可以被重写

  3. 被final修饰的类: 不可以被继承

package com.dylan.bao;

 public final class Diamonds {
     private final int weight = 100;
     public void change(){
         this.weight = 200; // 报错. 被final修饰. 不可以被修改
     }
     public final void bling(){
         System.out.println("钻石是不灵不灵的");
     }
 }
package com.dylan.bao;

 public class PinkDiamonds extends Diamonds {
      public  void bling(){ // 被final修饰的方法不可以被重写
         System.out.println("钻石是不灵不灵的");
     }
 }

抽象类

package com.dylan.bao;

 public abstract class Animal { // 含有抽象方法的类必须是抽象类

     public abstract void chi(); // 抽象方法

     public void dong(){ // 抽象类也可以有正常的方法
         System.out.println("估拥");
     }
 }
package com.dylan.bao;

 public class Cat extends Animal{ // 继承抽象类, 必须重写抽象方法
     public void chi(){ // 猫是有确定的吃的方式的. 
         System.out.println("猫吃鱼");
     }
 }

这样间接的. 我们使用抽象类就对子类进行了约束. 要求子类必须有一个xxx方法.

接口

接口其实就是一种特殊的抽象类. 接口里所有的方法都是抽象方法.

package com.dylan.bao;
 ​
 /**
  * 注意: 这里不能写class. 要写interface
  */
 public interface Valuable {
     void sell(); // 由于接口里所有的方法都是public abstract  所以不放修饰符. 默认是全局抽象
 }

接口既然全都是抽象方法, 那如何使用呢? 和抽象类一样. 也要借助于子类. 但是, 接口终归是接口, 这玩意不是类. 所以, 记住

继承接口的只能是接口.

那接口和类之间怎么产生关系呢?

类可以实现接口(implements), 实现和继承其实从语义上讲是一样的.

比如, 熊猫是一种值钱的东西

package com.dylan.bao;

 // 类实现接口. 类继承类. 接口继承接口. 
 public class Panda implements Valuable {
     @Override  // 重写
     public void sell() {
         System.out.println("熊猫可以卖个好价钱");
     }
 }

类与类之间: 继承关系
接口和接口: 继承关系
类和接口: 实现关系

在这里. 我们把panda称之为Valuable的一个实现类, 接口和接口实现类之间其实就是一种另类的继承关系. 接口的实现类必须实现(重写)接口中所有的抽象方法. 否则这个类必须是一个抽象类. 接口也可以对类进行约束.

用抽象类不也挺好的么. 为什么非要搞出一个接口?

java中一个类只能有一个父类. 这就导致一个问题. 说, 熊猫是一种值钱的东西. 熊猫继承值钱的东西. 但是, 熊猫还是一个受保护的东西啊. 熊猫再继承受保护的东西. 那就不对了. java是单继承啊. 语法上不让你这么写. 怎么办? 接口可以多实现~

package com.dylan.bao;

 public class Panda extends Animal implements Valuable, Protectable {

     @Override
     public void sell() {
         System.out.println("熊猫可以卖个好价钱");
     }

     @Override
     public void protect() {
         System.out.println("熊猫应该被保护起来");
     }

     @Override
     public void chi() {
         System.out.println("熊猫吃竹子");
     }
 }
package com.xyq.bao;

 public class Test {
     public static void main(String[] args) {
         Animal ani = new Panda();
         ani.chi();

         Valuable val = new Panda();
         val.sell();

         Protectable pro = new Panda();
         pro.protect();

         Panda pan = new Panda();
         pan.chi();
         pan.protect();
         pan.sell();
     }
 }

一个类可以实现多个无关的接口

一个接口可以被多个无关的类实现

接口最重要的作用: 把不想关的两个物体连接起来. 通过接口连接.
接口里都是方法么? 不, 接口里也有变量, 但是接口里的变量全部都是全局静态常量.

public interface Valuable{
     double money = 100; // 相当于 public static final double money = 100;
 }
 public class Client{
     public static void main(String[] args){
         System.out.println(Valuable.money); //可以使用类名直接调用. 静态的
         Valuable.money = 200; // 报错. 不可以被修改
     }
 }

注意: 我们很少会在接口里设置变量. 更多的是使用接口来统一数据类型. 和对实现类进行约束.

equals 和 ==

package com.dylan.bao;

 public class Cat {
     private String name;
     private String color;

     public Cat(String name, String color) {
         this.name = name;
         this.color = color;
     }

     public static void main(String[] args) {
         Cat c1 = new Cat("小花", "红色");
         Cat c2 = new Cat("小花", "红色");

         System.out.println(c1 == c2); //false
         System.out.println(c1.equals(c2)); //false

     }
 }

== 判断的是两只猫是不是同一只猫. 判断内存地址

equals我们调用的是object里的equals, 和==没有区别

package com.dylan.bao;

 public class Cat {
     private String name;
     private String color;

     public Cat(String name, String color) {
         this.name = name;
         this.color = color;
     }

     // 手动给出equals方法
     public boolean equals(Cat c){
         if(this.name == c.name && this.color == c.color){
             return true;
         } else {
             return false;
         }
     }

     public static void main(String[] args) {
         Cat c1 = new Cat("小花", "红色");
         Cat c2 = new Cat("小花", "红色");

         System.out.println(c1 == c2); //false
         System.out.println(c1.equals(c2)); //true

     }
 }

搞定. 我们通过==来判断两只猫是否是同一只猫. 通过equals来判断两只猫长的是不是一样.

接下来, 看一个玄学问题

package com.dylan.bao;

 public class Test {

     public static void main(String[] args) {
         String a1 = "竹鼠";
         String a2 = "竹鼠";

         System.out.println(a1 == a2); // true
         System.out.println(a1.equals(a2)); // true

         String a3 = new String("节奏狗");
         String a4 = new String("节奏狗");

         System.out.println(a3 == a4); // false
         System.out.println(a3.equals(a4)); // true

     }
 }

为什么会这样. 因为字符串是我们使用频率最高的一种数据类型. java会自动帮我们对字符串进行缓存. 发现一样的字符串了就不再创建新的了. 所以a1和a2内存地址是一样的. 所以两个都是真. a3在创建的时候new了一次. a4也new了一次. 此时, “节奏狗” 三个字被缓存. 但是. new的时候是要创建对象的. 对象再引入”节奏狗”三个字. 所以. 两个对象的地址是不一样的. 但是内容是一样的.

综上, 我们在判断两个字符串是否一致的时候, 一定要用equals 这样就是很稳定的判断内容是否一样.

toString()

package com.dylan.bao;

 public class Cat {
     private String name;
     private String color;

     public Cat(String name, String color) {
         this.name = name;
         this.color = color;
     }

     public String toString(){
         return "一只"+this.color+"猫, 名字是:"+this.name;

     }

     public static void main(String[] args) {
         Cat c = new Cat("韩梅梅", "绿色");
         System.out.println(c); // 一只绿***, 名字是:韩梅梅

     }
 }

instanceof

package com.xyq.bao;

 public class Test {

     public static void main(String[] args) {
         Animal a = new Cat();

         if(a instanceof Cat){
             System.out.println("这个动物是一只猫");
         } else {
             System.out.println("这个动物不是一只猫");
         }
     }
 }

内存分析

package com.xyq.bao;

 public class Person {
     private String name;
     private int age;
     private String address;

     public static int num = 0;

     public Person(String name, int age, String address){
         this.name = name;
         this.age = age;
         this.address = address;

         num++;
     }

     public static void main(String[] args) {
         Person p1 = new Person("少林寺", 18, "河南嵩山");
         Person p2 = new Person("吐鲁番", 12, "新疆");
         Person p3 = new Person("海南岛", 16, "海南");
         Person p4 = new Person("北戴河", 4, "河北");
         System.out.println(Person.num);
     }
 }

在java中一共会分为4块内存区域. 分别是: 堆, 栈, 代码区, 数据区.

  • 堆: 放对象. new的东西都在这里

  • 栈: 局部变量. 基础数据类型变量.

  • 代码区: 类.

  • 数据区: 常量池, 静态变量.

常量池: 我们前面讲equals的时候. 说过java会帮我们缓存一些数据. 这些数据就放在常量池里

接下来.我们先分析第一句话

Person p1 = new Person(“少林寺”, 18, “河南嵩山”);

其余的同理

参数传递

基本数据类型是值传递

package com.xyq.bao;

 public class TestMethod {

     public static void change(String b){
        b = "aaa";
        int hashCode = System.identityHashCode(b);
        System.out.println(hashCode); //257895351
    }

    public static void main(String[] args) {
        String a = "bbb";
        change(a);
        System.out.println(a); // 10
        int hashCode = System.identityHashCode(a); // 查看到虚拟机映射的地址 HASHCODE
        System.out.println(hashCode); // 1929600551
    }
 }

对象是引用传递

public class Temp {

   public static void change(Person p){
        p.setName("ABC");
        System.out.println(p); // Person@f5f2bb7
        int hashCode = System.identityHashCode(p); // 查看到虚拟机映射的地址 HASHCODE
        System.out.println(hashCode); // 257895351 
    }

    public static void main(String[] args) {
        Person p = new Person("小明", "male");
        change(p);
        System.out.println(p.name); // ABC
        int hashCode = System.identityHashCode(p); // 查看到虚拟机映射的地址 HASHCODE
        System.out.println(p); // Person@f5f2bb7 
        System.out.println(hashCode);  // 257895351 
    }
}

异常处理

异常类的根: Throwable

两个分支: Error和Exception

Exception又分为RuntimeException和其他Exception

RuntimeException表示运行时异常. 只有跑起来才知道错了.

其他Exception: 不用跑就知道错了

Error: 系统级错误. 程序员一般不好处理.

try ... catch

在idea里可以ctrl+alt+t快速的给代码加上try…catch

package com.dylan.bao;

 public class CreateException {

     public static void main(String[] args) {

         try {
             System.out.println(1/0);
         } catch (ArithmeticException e) {
             System.out.println("出现错误, 请联系系统管理员");
         } 
     }
 }

throws和throw

public class Test {
     public static void chu(int a, int b) throws Exception{ //告诉外面, 我要扔出来一个错误
         if (b==0){ // 0不能做除数
             // 主动抛异常
             throw new Exception("你不可以给我一个0"); // 真正的向外抛出一个异常
         } else {
             System.out.println(a/b);
         }
     }

     public static void main(String[] args) {

         try {
             chu(1, 2);
         } catch (Exception e) {
             e.printStackTrace();
             // System.out.println(e.getMassage());
         }
     }
 }

如果方法中主动向外抛异常, 那么这个方法必须要通知外界去处理异常, 也就是说, 方法内部有throw, 方法的声明就一定有throws. 反之则不一定.

自定义异常

模拟男女澡堂子, 女方不能进男澡堂

public class Person {
    String name;
    String gender;

    public Person(String name, String gender) {
        this.name = name;
        this.gender = gender;
    }

}
public class Zaotangzi {
    public void male(Person p) throws GenderException {
        if(p.gender.equals("男")){
            System.out.println("Welcome!");
        }
        else{
            throw new GenderException("性别不对, 这里是男澡堂子!");
        }
    }
}
public class GenderException extends Exception{
    public GenderException(String msg){
        super(msg);
    }
}
public class Test {
    public static void main(String[] args) throws GenderException {
        Person p = new Person("小明", "女");
        Zaotangzi z = new Zaotangzi();

        z.male(p);
    }
}