方法
public/protected/private stastic void/int methodName(int inp){
return inp
}
访问修饰符 | 本类 | 同包 | 跨包子类 | 其它 |
---|---|---|---|---|
private | 可 | |||
默认(不加修饰符) | 可 | 可 | ||
protected | 可 | 可 | 可 | |
public | 可 | 可 | 可 | 可 |
案例:
public class HelloWorld {
// 1. 无参数无返回值方法
public void printStar() {
System.out.println("*********");
}
// 2. 有参数有返回值方法
public float myFunc(float a,float b){
printStar(); // 3. 不在主方法中调用方法,就可以不用 new 一个
return a+b;
}
public static void main(String[] args) {
// 4. 在main中调用方法:
HelloWorld helloWorld = new HelloWorld();
helloWorld.printStar();
float c= helloWorld.myFunc(1.2f,1.5f);
System.out.println(c);
helloWorld.printStar();
}
}
重载
方法的重载:
- 方法名相同,但入参列表不同。
- 入参列表不同指的是入参的类型不同(顺序、个数、类型)。如果按顺序看类型和个数相同但参数名不同,是不能重载的。例如
func(String a,int b)
和func(String c, int d)
认为是入参相同。 - 返回值可以不同,访问修饰符可以不同。
// 一个重载的例子
public class D {
public void func(int a) {
System.out.println("int");
}
public void func(String a) {
System.out.println("String");
}
public static void main(String[] args) {
D d = new D();
d.func(1);
d.func("1");
}
}
可变参数类型
可变参数列表
public class HelloWorld {
public int get_sum(int... n) { // 可变参数列表
int sum = 0;
for (int i : n) {
sum += i;
}
return sum;
}
public static void main(String[] args) {
HelloWorld helloWord = new HelloWorld();
System.out.println(helloWord.get_sum(1));
System.out.println(helloWord.get_sum(1, 2, 3));
}
}
可变参数类型2
public class HelloWorld {
public boolean isIn(int a, int... n) { // 可变参数必须放到最后。这个n可以传入数组,因此不能再写一个同名方法重载另一个n是数组的方法了。
for (int i : n) {
if (a == i) {
return true;
}
}
return false;
}
public static void main(String[] args) {
HelloWorld helloWord = new HelloWorld();
System.out.println(helloWord.isIn(1, 1, 2, 3, 4));
System.out.println(helloWord.isIn(5, 1, 2, 3));
}
}
可变参数列表:
- 可变参数必须放到最后。
- 可变参数位置的可以传入数组。因此不能再写一个同名方法重载另一个n是数组的方法了(因为已经有了这样一个方法了)。
- 多个同名方法同时满足时,可变参数最后被调用。例如,两个方法
func(int a, int b)
,func(int... a)
,那么调用func(1,2)
会优先调用第一个。
方法的传值问题:在方法中改变一个变量的值,方法外会怎样?
- (与 Python 一样)
- 如果传入int等,方法中改变值后,方法外不变
- 如果传入数组,方法外会跟着变
- 如果传入自定义对象,方法外会跟着变
初始化
// 下面是一个声明+初始化对象的语言。
Cat one = new Cat();
以上语句完成了下面3给步骤
- 声明一个对象:在栈里面开辟一个空间,内容为 null
- 初始化一个对象:在堆里面开辟一个空间,初始化一个对象。
- 通过赋值操作,把栈和堆关联起来:堆空间的内存号存入到栈空间。
构造方法
- 构造方法的方法名必须与类名相同
- 构造方法没有返回值
- 构造方法可以有参数或无参数,因此可以重载。
- 构造方法只能在实例化时被调用。不能在其它地方被调用,不能被普通方法调用。
- 如果没有显式指定构造方法,会自动指定一个无入参的构造方法
- 构造方法可以用相互调用,用
this(args)
- (不推荐)可以写一个同名的普通方法,不会有语法错误,但不推荐这么干。
package com.guofei.learnjava;
public class Cat {
// 属性:
String name;
int month;
double weight;
String species = "中华田园猫";
public Cat() { // 无参数的构造方法
System.out.println("无参构造一个猫"); // 构造方法不能有返回值
}
// 构造方法可以多态
public Cat(String name) { // 有参数的构造方法
this(); // 构造方法可以相互调用,必须放第一行
System.out.println("有参构造一个猫");
this.name = name; // (没啥用:如果不加this,会自动寻找最近的name赋值,就不是属性那个name,改名也可以解决此问题)
this.speak(); // 构造方法可以引用普通方法。这里 this 可以省略
}
//方法
public void speak() {
System.out.println(name + ": 喵喵喵");
}
}
使用:(下面两个会执行不同的构造方法)
Cat one = new Cat();
Cat two = new Cat("花花");
封装
隐藏某些信息,以接口的方式提供给外界使用。
- 属性设为 private
- 创建 getter/setter 方法,方法名如
setName
- 在 getter/setter 中做属性访问控制
- 如果构造函数中给隐藏的属性赋值了,建议调用set方法赋值,以复用变量检查的代码。
- getter/setter 可以分别省略,以达到只读/只写效果
public class Cat {
private String name;
private int month;
public void setMonth(int month) {
if (month <= 0) {
System.out.println("年龄不能为负");
} else {
this.month = month;
}
}
public int getMonth() {
return this.month;
}
}
static
static 归属于类。
- 静态属性。在一个对象中改了静态属性后,所有对象以及类的属性都会变。(这与Python有些区别,Python的静态属性在赋值后不按照静态属性的逻辑来)
- 静态方法。更推荐用类来调用静态方法,而不是用对象来调用。
要点
- 方法内不能定义 static 属性,只能定义 finnal 属性。
- 方法可以对 static 做赋值/读取。
- 静态方法不能调用动态属性,也不能调用动态方法。(因为动态属性和动态方法是属于对象的)
- 解决方案:可以实例化后调用。
动态代码块和静态代码块
public class Cat {
public Cat() {
System.out.println("宠物猫来了!");
}
{
System.out.println("构造代码块,在构造的时候会被执行");
}
static {
System.out.println("静态代码块,比动态代码块先执行,且只会被调用一次");
}
}
// 调用:
Cat cat1=new Cat();
Cat cat2=new Cat();
静态代码块,比动态代码块先执行,且只会被调用一次 构造代码块,在构造的时候会被执行 宠物猫来了! 构造代码块,在构造的时候会被执行 宠物猫来了!
要点:
- 普通代码块:在方法中的代码块,在代码块中定义的变量,作用范围仅限于当前代码块。
- 类代码块在构造前执行,包括静态代码块、动态代码块。
- 静态代码块先执行(与位置无关)
- 动态代码块每次构造都被执行,静态代码块只会执行一次。
- 与方法一样,静态代码块只能调用静态属性和静态方法。
继承
继承
// A.java
public class A {
private int i;
protected int j;
public void func1() {
System.out.println("Class A");
}
}
// B.java
public class B extends A {
public int z;
public void func2(){
super.func1(); // 注意这个 super 方法
System.out.println("Class B");
}
}
// C.java
public class C {
public static void main(String []args){
A a=new A();
B b=new B();
System.out.println("Hello world!");
System.out.println(a instanceof A);
System.out.println(b instanceof B);
b.func1();
}
}
注意
- 只能继承一个类
重写 (Override):子类可以写一个同名的方法,覆盖掉父类的同名方法。
- 方法名必须相同。如果不同,其实是重载了。
- 返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 参数列表与被重写方法的参数列表必须完全相同。
- 权限 子类方法的访问权限必须大于或等于父类方法的访问权限。(修饰符和权限的关系见于上表)例如:如果父类的 public 在子类中重写不能是 protected
- final 方法不能被重写。
- static 方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 构造方法:子类构造方法必须调用父类构造方法。如果不显式声明如何调用父类构造方法,会默认调用父类的无参构造方法。如果显式声明,会按照显式声明来。(见代码)。显式声明必须放到第一行。
this()
和super()
都必须在第一行,因此在不能同时出现在同一个构造方法中- 如果不能继承一个方法,则不能重写这个方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 执行顺序: 父类静态属性 -> 父类静态代码块 -> 子类静态代码块 -> 父类构造代码块->父类构造方法 -> 子类构造代码块 -> 子类构造方法
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个方法,则不能重写这个方法。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
重载 (overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型呢?可以相同也可以不同。
3
// Animal.java
public class Animal {
public String name;
public Animal() {
System.out.println("构造一个 Animal");
}
public Animal(String name) {
System.out.println("构造了一个 Animal:" + name);
this.name = name;
}
public void eat() {
System.out.println(this.name + " 最近没有食欲!");
}
public void eat(String food) {
System.out.println(this.name + " 最近没有食欲!");
}
}
// Dog.java
public class Dog extends Animal {//只能继承一个类
public Dog() {
System.out.println("构造一个 Dog");
}
public Dog(String name) {
// 子类构造方法必须调用父类构造方法。
// super(); // 如果不显式声明会默认调用父类的无参构造方法。
super(name); // 如果显式声明,会按照显式声明来。
// 显式声明必须放到第一行。
}
public void eat(String food) { // 方法名、返回值类型必须相同。
super.eat(); // 可以用 super 调用 父类的 eat()
System.out.println("dog" + this.name + " 吃" + food);
}
public int eat(String food, int volume) { // 这个其实不是重写,而是重载
this.eat(food);
return volume - 1;
}
}
使用:
Dog dog1 = new Dog();
dog1.eat(); // Dog 类并没有重写此种参数的 eat(),因此调用的是父类
System.out.println("=========");
Dog dog2 = new Dog("点点");
dog2.eat("饼干");
int left = dog2.eat("牛奶", 10);
System.out.println("牛奶剩下"+left);
输出:
构造一个 Animal 构造一个 Dog null 最近没有食欲! ========= 构造了一个 Animal:点点 点点 最近没有食欲! dog点点 吃饼干 点点 最近没有食欲! dog点点 吃牛奶 9
object类
object 类是所有类的父类。在 java.lang
这个包里面。
常用方法
equals
默认是判断内存指向是否一致。可以重写,例如String
类型的 equals 就重写为判断内容是否相同(但是字符串的==
还是比较的是内存指向)
// 重写 equals的案例
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
Animal tmp = (Animal) obj;
return this.name.equals(tmp.name);
}
// 还可以这样重写
public boolean equals(Animal obj) {
if (obj == null) {
return false;
}
Animal tmp = (Animal) obj;
return this.name.equals(tmp.name);
}
toString()
,对应print(dog2), print(dog2.toString())
,默认打印一个类似com.guofei.work2.Dog@a09ee92
这样的东西。public String toString() { return "Animal{" + "name='" + name + '\'' + '}'; }
hashCode()
对象的哈希代码值getClass()
返回对象所属的类
final
package com.guofei.work2;
public final class Cat {// final class:不允许被继承
final String name; //只能:1. 定义时赋值 2. 在构造函数中赋值,3. 构造代码块中被赋值。
final static String author="guofei9987"; //final static 连用,可以防止被随意篡改。
public Cat(){
this.name="name"; // final 一个属性后,必须赋值。
}
public final void eat(){ // final 方法:不允许子类被重写,但可以被子类使用。
final int a=1; //final 修饰局部变量:第一次赋值后不允许再更改。
System.out.println("");
}
}
- 可以修饰类,表示类不能被继承
- 可以修饰方法,表示方法不能被重写。但是可以被子类继承和使用。final不能修饰构造方法。
- 可以修饰类的成员变量,只有3种赋值方法: 1. 定义时赋值 2. 在构造函数中赋值,3. 构造代码块中被赋值。赋值后不允许更改。
- 可以修饰局部变量,第一次赋值后不允许再更改。
包
命名规则:
- 小写
- 按照域名倒叙
包文件管理,例如,我们创建了一个 Cat
类,然后想要另创建一个机器猫也叫 Cat
. 这就需要另建一个包。
建立两个package,分别是如下内容:
// com.guofei.animal
package com.guofei.animal;
public class Cat {
public Cat(){
System.out.println("宠物猫来了!");
}
}
// com.guofei.robot
package com.guofei.robot;
public class Cat {
public Cat(){
System.out.println("机器猫来了!");
}
}
调包有以下几种:
import com.guofei.robot.Cat; // 方法1:加载指定类
import com.guofei.animal.*; // 方法2:加载所有类,会被精确指定的(上一条语句)覆盖,这个覆盖与 import 的顺序无关
// 方法3:使用时指定:
com.guofei.animal.Cat cat2=new com.guofei.animal.Cat();
// 一个坑:import 只能找到指定目录下的类,而找不到子文件夹的类。
多态
// 向上转型
Animal one = new Animal();
Animal two = new Cat();
Animal three = new Dog();
one.eat();
two.eat();
three.eat();
// two.run(); // 报错!因为是 Animal 类型,需要向下转型:
Cat four = (Cat) two;
four.run();
System.out.println(two.getClass()); // Cat
System.out.println(two instanceof Cat); // true
System.out.println(two instanceof Animal); // true
多态的2个例子:
public class Master {
// 多态例子1,主人喂宠物后,又根据宠物类型不同 做另一个动作。
public void feed(Animal obj) {
obj.eat();
if (obj instanceof Dog) {
Dog tmp = (Dog) obj;
tmp.sleep();
}
}
// 多态例子2,主人根据自己时间养不同的宠物。
public Animal pet;
public Animal adoptPet(boolean hasTime) {
if (hasTime) {
pet = new Dog();
} else {
pet = new Cat();
}
return pet;
}
}
抽象类
public abstract class Animal{ // 如此,就不能 new Animal 了。
public abstract void play(); // 抽象方法。
// 1. 子类必须重写,除非子类也是抽象类。
// 2. 所在类必须是抽象类。
}
设计模式
单例模式
使用场景
- 创建对象占用资源较多。
- 对某个资源要求统一读写,如配置信息、打印机
- 多个实例会引起逻辑错误,如primary_key 生成器。
饿汉式:
public class Printer {
// 1. 私有构造
private Printer() {
}
// 2. 私有静态实例
private static Printer instance = new Printer();
// 3. 提供一个接口,返回静态实例对象
public static Printer getInstance() {
return instance;
}
}
// 测试:
Printer one=Printer.getInstance();
Printer two=Printer.getInstance();
System.out.println(one==two);
懒汉式:(使用时才会创建)
public class Printer {
// 1. 私有构造
private Printer() {
}
// 2. 私有静态实例
private static Printer instance = null;
// 3. 提供一个接口,返回静态实例对象
public static Printer getInstance() {
if (instance == null) {
instance = new Printer();
}
return instance;
}
}
比较
- 饿汉用空间换时间
- 懒汉用时间换空间
- 懒汉存在多线程风险。
接口
问题
- 前面说了,只能继承一个
- 很多时候,没有继承关系的类,想让他们又联系。例如 猫和机器猫都能跑,我们想把这个跑的动作关联起来。
public class Animal {
}
public interface IRunner {
// 每个动作消耗卡路里。
int calorie = 20; // 属性,默认public static final
public void run(); // 必须被重写
default void jump(){ // 加上 default 后,就不必被重写了。
System.out.println("跳起");
}
static void stop(){
System.out.println("停下来");
}
}
public interface IPlayer {
public void run();
}
public class Cat extends Animal implements IRunner, IPlayer { // 多个 interface
@Override
public void run() {
System.out.println("猫咪跑跑");
}
@Override
public void jump() {
System.out.println("猫咪会跳");
}
}
public class RobotCat implements IRunner{
@Override
public void run() {
System.out.println("机器猫用轮子跑");
}
}
内部类
可以在类、方法中定义另一个类。
成员内部类
public class Person {
int age;
// 1. 成员内部类
class Heart {
public String beat() {
return "心脏在跳动";
}
}
public Heart getHeart() {
return new Heart();
}
}
// 获取:
Person.Heart myHeart;
// 获取方法1:
myHeart = new Person().new Heart();
// 获取方法2:
myHeart = p1.new Heart();
// 获取方法3:
myHeart = p1.getHeart();
内部类也可以添加修饰符 public/private 等。
内部类可以调用外部类的字段、方法:
public class Person {
int age;
public void eat() {
System.out.println("人会吃东西");
}
class Heart {
public void beat() {
eat(); // 内部类可以使用外部类的方法
System.out.println(age + "岁的心脏在跳动"); //内部类可以使用外部类的字段
// Person.this.age // 如果有同名歧义,可以精确指定。
}
}
}
Person p1 = new Person();
p1.age = 10;
Person.Heart myHeart = p1.new Heart();
myHeart.beat();
静态内部类
public class Person {
public void eat() {
System.out.println("人会吃东西");
}
public static void run(){
System.out.println("人会跑");
}
static class Heart {
public void beat() {
run(); // 静态内部类只能调用外部类的静态方法
// eat(); // 报错,静态内部类不能调用外部类的非静态方法。
new Person().eat(); // 作为解决,还可以 new 一个对象来调用
}
}
}
// 调用:
Person.Heart myHeart = new Person.Heart();
方法内部类
定义在方法内的类。
- 作用范围在方法内
- 方法内的成员,不能是 public、private、protected、static
- 可以 final,abstract
匿名内部类
有点儿想匿名函数,使用时定义一个类。
- 不能用 public、private、protected、static、abstract 修饰(这么修饰也没意义)
- 不能用构造方法,可以用构造代码块
- 不能有静态成员(也没有意义)
// Person.java
public abstract class Person {
public abstract void eat() ;
}
// Test.java
public class Test {
public void func(Person person) {
person.eat();
}
public static void main(String[] args) {
Test tst = new Test();
tst.func(new Person() {
@Override
public void eat() {
System.out.println("吃");
}
});
}
}