编写Java代码的151个建议
- 编写高质量代码:改善Java程序的151个建议 【这个有本书,看书就好】
https://blog.csdn.net/guorui_java/category_10115902.html
面向对象
何为面向对象,面向对象是OOP。OOP 的英文全称是 Object Oriented Programming,要理解它的话,就要先理解面向对象,要想理解面向对象的话,就要先理解面向过程,因为一开始没有面向对象的编程语言,都是面向过程。
- 面向过程:面向过程是一种以过程为中心的编程思想,都是以什么正在发送作为主要目标进行编程。
- 面向对象:面向对象设计以对象为核心,对象本身拥有属性、方法,其可以自发的去做某些事情。面向对象的思维的主要逻辑特点是逻辑分析思维,认为万物皆有边界。如同”世界”这个词语一样,通过寻找边界定义一个事物,其是具体的、名词性的;然后在探讨事物内部的组成部分,通过封装不变性,开放变化性,以增强系统的柔韧性和灵活性。
- 举个简单的例子区分一下面向过程和面向对象:有一天你想吃小炒肉了。那你此时有2个选择。
- 第一个选择:自己买食材,油、肉、调料等等,自己做。
- 第二个选择:到饭店去,只需要告诉老板想吃啥就可以了。
- 第一个选择就是面向过程,第二个选择就是面向对象。
- 面向过程有什么劣势:现在想吃的是小炒肉,然后忽然又想吃拔丝地瓜了,那还要重写买食材
- 面向对象相比之下的优势:如果不想吃小炒肉了,只需要对老板说,把我那个小炒肉换成拔丝地瓜。
- 面向过程是流程化的,一步一步,上一步做完了,再做下一步。
- 面向对象是模块化的,我做我的,你做你的,我需要你做的话,我就告诉你一声。我不需要知道你到底怎么做,只看功劳不看苦劳。
不过,如果追到底的话,面向对象的底层其实还是面向过程,只不过把面向过程进行了抽象化,封装成了类,方便我们的调用。
类/对象
对象可以是现实生活中看得见的任何物体、也可以是虚拟想象中的物体。
- Java通过类(class)来定义这些物体,这些物体有什么状态,通过字段来定义,比如一只猪的颜色是彩色;这些物体有什么行为,通过方法来定义,比如猪会吃、会睡觉。
定义一个简单的类 ```java public class Person { private String name; private int age; private int sex;
private void eat() { }
private void sleep() { }
}
- 一个类可以包含:字段(Field)、方法(Method)、构造方法(Constructor)- 在Person类中,字段有三个,分别是name、age、sex,它们也称为成员变量——在类内部但在方法外部,方法内部的叫临时变量。- 成员变量有时候也叫做实例变量,在编译时不占用内存空间,在运行时获取内存,也就是说,只有在对象实例化(new Person())后,字段才会获取到内存,这也正是它被称作“实例”变量的原因。- 方法 2 个,分别是 eat()、sleep() 表示 Person 这个对象可以做什么,也就是吃饭睡觉。- 每个类都有默认的构造方法,也就是无参构造方法,当自定义了构造方法之后,默认的无参构造方法则会消失。- **创建对象**```javapublic class PersonMain {public static void main(String[] args) {Person person = new Person();}}// 所有对象在创建的时候都会在堆内存中分配空间
变量
- 局部变量
- 在方法体内声明的变量叫做局部变量,该变量只能在该方法内使用,在类的其他方法中不知道该变量。
```java
public class LocalVariable {
public static void main(String[] args) {
} }int a = 10; int b = 10; int c = a + b; System.out.println(c);
- 其中 a、b、c 就是局部变量,它们只能在当前这个 main 方法中使用。
- 声明局部变量时的注意事项:
- 局部变量声明在方法、构造方法或者语句块中。
- 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,将会被销毁。
- 访问修饰符不能用于局部变量。
- 局部变量只在声明它的方法、构造方法或者语句块中可见。
- 局部变量是在**栈**上分配的。
- 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。
- **成员变量**
- 在类内部但在方法体外声明的变量称为成员变量,或者实例变量。之所以称为实例变量,是因为该变量只能通过类的实例(对象)来访问。
```java
public class InstanceVariable {
int data = 88;
public static void main(String[] args) {
InstanceVariable iv = new InstanceVariable();
System.out.println(iv.data); // 88
}
}
- 其中 iv 是一个变量,它是一个引用类型的变量。new 关键字可以创建一个类的实例(也称为对象),通过”=”操作符赋值给 iv 这个变量,iv 就成了这个对象的引用,通过 iv.data 就可以访问成员变量了。
- 声明成员变量时的注意事项:
- 成员变量声明在一个类中,但在方法、构造方法和语句块之外。
- 当一个对象被实例化之后,每个成员变量的值就跟着确定。
- 成员变量在对象创建的时候创建,在对象被销毁的时候销毁。
- 成员变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息。
- 成员变量可以声明在使用前或者使用后。
- 访问修饰符可以修饰成员变量。
- 成员变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把成员变量设为私有。通过使用访问修饰符可以使成员变量对子类可见;成员变量具有默认值。数值型变量的默认值是 0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。变量的值可以在声明时指定,也可以在构造方法中指定。
- 静态变量
通过 static 关键字声明的变量被称为静态变量(类变量),它可以直接被类访问。 ```java public class StaticVariable { static int data = 99;
public static void main(String[] args) {
System.out.println(StaticVariable.data); // 99} }
- 其中 data 就是静态变量,通过类名.静态变量就可以访问了,不需要创建类的实例
- 声明静态变量时的注意事项:
- 静态变量在类中以 static 关键字声明,但必须在方法构造方法和语句块之外。
- 无论一个类创建了多少个对象,类只拥有静态变量的一份拷贝。
- 静态变量除了被声明为常量外很少使用。
- 静态变量储存在**静态存储区**。
- 静态变量在程序开始时创建,在程序结束时销毁。
- 与成员变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型。
- 静态变量的默认值和实例变量相似。
- 静态变量还可以在静态语句块中初始化。
- **常量**
- 在 Java 中,有些数据的值是不会发生改变的,这些数据被叫做常量——使用 final 关键字修饰的成员变量。常量的值一旦给定就无法改变
- 常量在程序运行过程中主要有 2 个作用:
- 代表常数,便于修改(例如:圆周率的值,final double PI = 3.14)
- 增强程序的可读性(例如:常量 UP、DOWN 用来代表上和下,final int UP = 0)
- **Java 要求常量名必须大写**
```java
public class FinalVariable {
static final String ZHU = "ZHU";
final String QIANG = "QIANG";
public static void main(String[] args) {
FinalVariable finalVariable = new FinalVariable();
System.out.println(finalVariable.ZHU);
System.out.println(finalVariable.QIANG);
}
}
方法
- 什么是方法:方法用来实现代码的可重用性,我们编写一次方法,并多次使用它。通过增加或者删除方法中的一部分代码,就可以提高整体代码的可读性。只有方法被调用时,它才会执行。
- 如何声明方法:方法的声明反应了方法的一些信息,比如可见性、返回值类型、方法名和参数。

- 访问权限:它指定了方法的可见性。Java 提供了四种访问权限修饰符 【代码测试】
- public:该方法可以被所有类访问。
- private:该方法只能在定义它的类中访问。
- protected:该方法可以被同一个包中的类,或者不同包中的子类访问。
- default:该方法如果没有使用任何访问权限修饰符,Java 默认它使用 default 修饰符,该方法只能被同一个包中类可见。
- 返回类型:方法返回的数据类型,可以是基本数据类型、对象和集合,如果不需要返回数据,则使用 void 关键字。
- 方法名:方法名最好反应出方法的功能【一般使用动词、类名则使用名词】,比如,我们要创建一个将两个数字相减的方法,那么方法名最好是 subtract。方法名最好是一个动词,并且以小写字母开头。如果方法名包含两个以上单词,那么第一个单词最好是动词,然后是形容词或者名词,并且要以驼峰式的命名方式命名。
- 一个方法可能与同一个类中的另外一个方法同名,这被称为方法重载。注意,只是名字相同,方法签名不同。
- 重写:则是位置不同,在不同的类中,子类重写父类的方法。
- 参数:参数被放在一个圆括号内,如果有多个参数,可以使用逗号隔开。参数包含两个部分,参数类型和参数名。如果方法没有参数,圆括号是空的。
- 方法签名:每一个方法都有一个签名,包括方法名和参数。
- 方法体:方法体放在一对花括号内,把一些代码放在一起,用来执行特定的任务。
- 方法有哪几种?
- 方法可以分为两种,一种叫预先定义方法,一种叫用户自定义方法。
- 预先定义方法:Java提供了大量的预先定义好的方法给我们使用。直接只用JDK提供的方法,使用的就是预先定义方法。
- 用户自定义方法:就是我们程序员自己定义的方法。
- 方法的定义与调用 【参见代码演示】
什么是实例方法:没有使用static关键字修饰的,但是在类中生声明的方法叫做实例方法,在调用实例方法之前,必须先创建类的对象。 ```java public class InstanceMethodExample { public static void main(String[] args) {
InstanceMethodExample instanceMethodExample = new InstanceMethodExample(); System.out.println(instanceMethodExample.add(1, 2));}
public int add(int a, int b) {
return a + b;} }
- add() 方法是一个实例方法,需要创建 InstanceMethodExample 对象来访问。
- 实例方法有两种特殊类型:
- getter 方法
- setter 方法
- getter 方法用来获取私有变量(private 修饰的字段)的值,setter 方法用来设置私有变量的值。
```java
public class Person {
String name;
private int age;
private int sex;
private void eat() {
}
protected void sleep() {
}
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 int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
}
抽象类和抽象方法
被abstract关键字修饰的类就为抽象类,在抽象类中被abstract修饰的方法称为抽象方法。抽象方法没有方法体。抽象类中不一定要有抽象方法,但是抽象方法必须在抽象类或者接口中。
public abstract class AbstractDemo { abstract void display(); }当一个类继承了抽象类之后,必须重写抽象方法。抽象方法不能是私有的。
public class MyAbstractDemo extends AbstractDemo { @Override void display() { System.out.println("MyAbstractDemo.display"); } public static void main(String[] args) { MyAbstractDemo myAbstractDemo = new MyAbstractDemo(); myAbstractDemo.display(); } }构造方法
如果你在一个类中没有看见构造方法,并不是因为构造方法不存在,而是被缺省了,编译器会给这个类提供一个默认的构造方法。往大的方面说,就是,Java 有两种类型的构造方法:无参构造方法和有参构造方法。
- 之所以叫它构造方法,是因为对象在创建的时候,需要通过构造方法初始化值——就是描写对象的那些状态,对应的是类中的字段。
- 构造方法的规则
- 构造方法的名字必须和类名一样;
- 构造方法没有返回类型,包括 void;
- 构造方法不能是抽象的、静态的、最终的、同步的,也就是说,构造方法不能通过 abstract、static、final、synchronized 关键字修饰。
简单解析一下最后一条规则。
- 由于构造方法不能被子类继承,所以用 final 和 abstract 修饰没有意义;
- 构造方法用于初始化一个对象,所以用 static 修饰没有意义;
- 多个线程不会同时创建内存地址相同的同一个对象,所以用 synchronized 修饰没有必要。
class class_name { public class_name(){} // 默认无参构造方法 public ciass_name([paramList]){} // 定义有参数列表的构造方法 … // 类主体 }
值得注意的是,如果用 void 声明构造方法的话,编译时不会报错,但 Java 会把这个所谓的“构造方法”当成普通方法来处理。 ```java public class Demo { void Demo() {
System.out.println("Demo.Demo");}
public static void main(String[] args) {
Demo demo = new Demo();} }
- 可以使用访问权限修饰符(private、protected、public、default)来修饰构造方法,访问权限修饰符决定了构造方法的创建方式。
- **默认构造方法**
- 如果一个构造方法中没有任何参数,那么它就是一个默认构造方法,也称为无参构造方法。
```java
public class Bike {
Bike() {
System.out.println("一辆自行车被创建");
}
public static void main(String[] args) {
Bike bike = new Bike();
}
}
- 在上面这个例子中,我们为 Bike 类中创建了一个无参的构造方法,它在我们创建对象的时候被调用。
- 通常情况下,无参构造方法是可以缺省的。编译器提供的是共有的默认无参构造方法

默认构造方法的目的主要是为对象的字段提供默认值,并且创建对象。
public class Person { private String name; private int age; public static void main(String[] args) { Person p = new Person(); System.out.println("姓名 " + p.name + " 年龄 " + p.age); } }什么是有参数构造方法:就是有参数的构造方法 【具体示例】
- 构造方法的重载:和普通方法重载一样,只是没有返回值。
- 构造方法和方法的区别:

如何复制对象?
初始化代码块的作用,用来初始化一些数据。 ```java public class Bike { List
list; {
list = new ArrayList<>(); list.add("1"); list.add("2");}
public static void main(String[] args) {
System.out.println(new Bike().list);} }
- 构造方法和代码块哪个先执行?
```java
public class Car {
Car() {
System.out.println("构造方法");
}
{
System.out.println("代码初始化块");
}
public static void main(String[] args) {
new Car();
}
}
// 代码初始化块
// 构造方法
- 对象在初始化的时候会先调用构造方法,这是毫无疑问的,只不过,构造方法在执行的时候会把代码初始化块放在构造方法中其他代码之前

- 代码初始化的规则
- 类实例化的时候执行代码初始化块;
- 实际上,代码初始化块是放在构造方法中执行的,只不过比较靠前;
- 代码初始化块里的执行顺序是从前到后的。 ```java
public class B extends A { B() { System.out.println(“子类构造方法”); }
{
System.out.println("代码初始化块");
}
public static void main(String[] args) {
new B();
}
}
class A { A() { System.out.println(“父类构造方法”); } }
<a name="aPtcE"></a>
# static
- static关键字,用来描述字段或者是方法是属于类的而非对象的,直接通过类名调用,只要这个类被加载了,就可以这样使用。
- 既然静态变量只加载一次。假设加里敦大学录取了一万名新生,那么在创建一万个 Student 对象的时候,所有的字段(name、age 和 school)都会获取到一块内存。学生的姓名和年纪不尽相同,但都属于加里敦大学,如果每创建一个对象,school 这个字段都要占用一块内存的话,就很浪费。
```java
public class Student {
String name;
int age;
static String school = "加里敦大学";
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Student s1 = new Student("LDL", 18);
Student s2 = new Student("ZQ", 16);
}
}
- s1 和 s2 这两个引用变量存放在栈区(stack),LDL+18 这个对象和ZQ+16 这个对象存放在堆区(heap),school 这个静态变量存放在静态区。

下面的例子 ```java public class Counter { int count = 0;
Counter() {
count++; System.out.println(count);}
public static void main(String args[]) {
Counter c1 = new Counter(); Counter c2 = new Counter(); Counter c3 = new Counter();} }
- 代码执行完毕之后,每个count都是1,因为这里的count是归属于对象的。也就是每个对象都持有一个自己的count
- 下面的这个则是归属于类的,每个对象使用的是同一个count
```java
public class Counter2 {
static int count = 0;
Counter2() {
count++;
System.out.println(count);
}
public static void main(String args[]) {
Counter2 c1 = new Counter2();
Counter2 c2 = new Counter2();
Counter2 c3 = new Counter2();
}
}
- 静态方法
- 静态方法属于这个类而不是这个类的对象;
- 调用静态方法的时候不需要创建这个类的对象;
- 静态方法可以访问静态变量。
- 静态方法不能访问非静态变量和调用非静态方法
静态代码块:只会执行一次,在类加载的时候进行执行。下面的这个Counter.static initializer只会输出一次 ```java public class Counter { int count = 0;
static {
System.out.println("Counter.static initializer");}
Counter() {
count++; System.out.println(count);}
public static void main(String args[]) {
Counter c1 = new Counter(); Counter c2 = new Counter(); Counter c3 = new Counter();} }
```java
public class StaticBlockDemo {
public static List<String> writes = new ArrayList<>();
static {
writes.add("1");
writes.add("2");
writes.add("3");
System.out.println("第一块");
}
static {
writes.add("4");
writes.add("5");
System.out.println("第二块");
}
public static void main(String[] args) {
System.out.println("StaticBlockDemo.main");
}
}
// 第一块
// 第二块
// StaticBlockDemo.main
静态内部类 单例设计模式的实现 ```java public class Singleton { private Singleton() {}
private static class SingletonHolder {
public static final Singleton instance = new Singleton();}
public static Singleton getInstance() {
return SingletonHolder.instance;} }
- 第一次加载 Singleton类时并不会初始化 instance,只有第一次调用 getInstance() 方法时 Java 虚拟机才开始加载 SingletonHolder 并初始化 instance,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。不过,创建单例更优雅的一种方式是使用枚举。
- 外部类初次加载,会初始化静态变量、静态代码块、静态方法,但不会加载内部类和静态内部类。
- 实例化外部类,调用外部类的静态方法、静态变量,则外部类必须先进行加载,但只加载一次。
- 直接调用静态内部类时,外部类不会加载。
- 第一,静态内部类不能访问外部类的所有成员变量;第二,静态内部类可以访问外部类的所有静态变量,包括私有静态变量。第三,外部类不能声明为 static。
- **其他知识点,会在JVM的class文件部分进行讲解**
```java
public class StaticDemo {
public static String CODE = "Hash";
public static String[] replaceCode() {
return CODE.split("");
}
}
class T {
public static void main(String[] args) {
System.out.println(StaticDemo.CODE);
System.out.println(StaticDemo.replaceCode());
}
}
this和super
- this指的是当前对象
- super指的是当前类的父类
- this和super都是对象级别的,而不是类级别的,在静态方法中不可以使用。
子类调用父类构造方法的时候,如果是默认无参构造,且父类也是默认无参构造,则隐式先调用父类构造方法,在调用子类构造方法。如果父类没有无参构造,则需要显式调用父类构造方法,必须写出来。 ```java public class ThisAndSuperTest extends SuperClass { String name = “icanci”;
public String getName() {
System.out.println(super.password); return this.name;} }
class SuperClass { String password = “password”; }
class TestMain { public static void main(String[] args) { ThisAndSuperTest thisAndSuperTest = new ThisAndSuperTest(); System.out.println(thisAndSuperTest.getName()); } }
```java
public class ThisAndSuperTest extends SuperClass {
String name = "icanci";
public ThisAndSuperTest() {
// 显式调用父类构造方法
super(1);
}
public String getName() {
System.out.println(super.password);
return this.name;
}
}
class SuperClass {
public SuperClass(int a) {
}
String password = "password";
}
class TestMain {
public static void main(String[] args) {
ThisAndSuperTest thisAndSuperTest = new ThisAndSuperTest();
System.out.println(thisAndSuperTest.getName());
}
}
可以在构造方法中调用this。但是不能调用自身,编译器会告警,并且this和super在构造方法中必须在第一行。因为一定是先进行构造方法,然后再执行代码。 ```java public class ThisTest { public ThisTest() {
this(1);}
public ThisTest(int a) { } }

- this也可以作为参数传递
```java
public class ThisTest {
public ThisTest() {
this(1);
}
public ThisTest(int a) {
}
public void hello() {
hello(this);
System.out.println("ThisTest.hello");
}
public void hello(ThisTest thisTest) {
System.out.println("ThisTest.hello");
}
public static void main(String[] args) {
ThisTest thisTest = new ThisTest();
thisTest.hello();
}
}
值传递和引用传递
值传递和引用传递。严格来说Java都是值传递,而引用传递就理解为引用的值。
int age = 18; String name = "LDL";age 是基本类型,值就保存在变量中,而 name 是引用类型,变量中保存的是对象的地址。一般称这种变量为对象的引用,引用存放在栈中,而对象存放在堆中。
当用 = 赋值运算符改变 age 和 name 的值时
age = 16; name = "ZQ";对于基本类型 age,赋值运算符会直接改变变量的值,原来的值被覆盖。
- 对于引用类型 name,赋值运算符会改变对象引用中保存的地址,原来的地址被覆盖,但原来的对象不会被覆盖。(原来的对象会由垃圾收集器进行收集)

基本数据类型的参数传递。Java 有 8 种基本数据类型,分别是 int、long、byte、short、float、double 、char 和 boolean,就拿 int 类型来举例。 ```json public class PrimitiveTypeDemo { public static void main(String[] args) {
int age = 18; modify(age); System.out.println(age);}
private static void modify(int age1) {
age1 = 30;} }
- main() 方法中的 age 为基本类型,所以它的值 18 直接存储在变量中。
- 调用 modify() 方法的时候,将会把 age 的值 18 复制给形参 age1。
- modify() 方法中,对 age1 做出了修改。
- 回到 main() 方法中,age 的值仍然为 18,并没有发生改变。
- **引用数据类型的传递。**以String为例。
```json
public class ReferenceTypeDemo {
public static void main(String[] args) {
String name = "LDL";
modify(name);
System.out.println(name);
}
private static void modify(String name1) {
name1 = "ZQ";
}
}
- 在调用 modify() 方法的时候,形参 name1 复制了 name 的地址,指向的是堆中”LDL”的位置。
- 当 modify() 方法调用结束后,改变了形参 name1 的地址,但 main() 方法中 name 并没有发生改变。


- 总结:
- Java 中的参数传递是按值传递的。
- 如果参数是基本类型,传递的是基本类型的字面量值的拷贝。
如果参数是引用类型,传递的是引用的对象在堆中地址的拷贝。 ```json public class RefrenceDemo {
public static void main(String[] args) {
RefrenceDemo refrenceDemo = new RefrenceDemo(); RefrenceDemoDemo refrenceDemoDemo = new RefrenceDemoDemo(); refrenceDemoDemo.setName("cccc"); System.out.println(refrenceDemoDemo); refrenceDemo.change(refrenceDemoDemo); System.out.println(refrenceDemoDemo);}
void change(RefrenceDemoDemo demoDemo) {
demoDemo.setName("demo");} }
class RefrenceDemoDemo { private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new StringJoiner(",").add("name=" + name).toString();
}
}
<a name="W0suq"></a>
# final
- 标志终结的、不可变的
- 使用final修饰的变量不可被修改、使用final修饰的方法不可被继承、使用final修饰的类不可被继承
- 比较典型的就是String,其是不可变的
- 理解不可变的,首先就要理解值传递和引用传递。
```json
public class FinalTest {
final int a = 1;
public void changeA(int a) {
a = 2;
System.out.println(a);
}
public static void main(String[] args) {
FinalTest finalTest = new FinalTest();
finalTest.changeA(finalTest.a);
System.out.println(finalTest.a);
}
}
- 如果不想方法里面更改拷贝的形参,则可以这样写

不能更改的是值以及引用,但是引用里面的值是可以改变的 ```json public class ReferenceTypeDemo2 {
public static void main(String[] args) {
final Pig pig = new Pig(); pig.setName("ZQ"); // pig = new Pig();} }
class Pig { private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- final 和 static 一起修饰的成员变量叫做常量,常量名必须全部大写。
```java
public class Pig {
private final int age = 1;
public static final double PRICE = 36.5;
}
instanceof
- 用法很简单,用来判断 (object) instanceof (type)
- 判断对象是否符合指定的类型,结果要么是 true,要么是 false。 ```java public class BaseOrder { }
public class GrabOrder extends BaseOrder { }
public class TradeOrder extends BaseOrder { }
```java
public class BaseOrderTest {
static List<BaseOrder> baseOrders = new ArrayList<>();
static {
baseOrders.add(new GrabOrder());
baseOrders.add(new TradeOrder());
baseOrders.add(new TradeOrder());
baseOrders.add(new GrabOrder());
baseOrders.add(new TradeOrder());
}
public static void main(String[] args) {
for (BaseOrder baseOrder : baseOrders) {
if (baseOrder instanceof GrabOrder){
// xxx
}else {
// xxx
}
}
}
}
可变参数
- Java5中提供了可变参数
- 在定义方法时,在最后一个形参后加上三点 …,就表示该形参可以接受多个参数值,多个参数值被当成数组传入。上述定义有几个要点需要注意
- 可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数
- 由于可变参数必须是最后一个参数,所以一个函数最多只能有一个可变参数
- Java的可变参数,会被编译器转型为一个数组
- 变长参数在编译为字节码后,在方法签名中就是以数组形态出现的。这两个方法的签名是一致的,不能作为方法的重载。如果同时出现,是不能编译通过的。可变参数可以兼容数组,反之则不成立
当使用可变参数的时候,实际上是先创建了一个数组,该数组的大小就是可变参数的个数,然后将参数放入数组当中,再将数组传递给被调用的方法 ```java public class VariableParameters { public static void main(String[] args) {
print("我"); print("我", "是"); print("我", "是", "Z"); print("我", "是", "Z", "Q");}
// public static void print(String s1,String… strs) {
for (String str : strs) { System.out.print(str); } System.out.println();}
public static void print(String… strs) {
for (String str : strs) { System.out.print(str); } System.out.println();} }
- 可变参数,相当于是数组,只是长度不确定,所以下面这样重载是错误的,编译器会报错。

- 可以这样进行重载,但是执行的时候就会报错,因为其不知道要调用的是哪个方法

- 避免可变参数的方法进行重载
```java
public class Client {
// 简单折扣运算
public static void calPrice(int price, int discount) {
float knockdownPrice = price * discount / 100.0F;
System.out.println("简单折扣后的价格为:" + formateCurrency(knockdownPrice));
}
// 复杂多折扣运算
public void calPrice(int price, int... discounts) {
float knowdownPrice = price;
for (int discount : discounts) {
knowdownPrice = (float) (price * discount / 100.0);
System.out.println("复杂折扣后的价格为:" + formateCurrency(knowdownPrice));
}
}
// 格式化货币
private static String formateCurrency(float price) {
return NumberFormat.getCurrencyInstance().format(price / 100);
}
public static void main(String[] args) {
// 499 元的货物打 75 折
Client.calPrice(49900, 75);
// 实际调用的是哪个呢?
}
}
- 这是一个计算商品价格的模拟类,带有两个参数的claPrice方法是一个简单的计算折扣的方法,这是单一的折扣方法,还有一个是比较复杂的计算方法,就是折上折,比如会员是9折,这天是你生日,再9折,那总计就是 0.9*0.9=0.81 折。这就是简单的业务逻辑。
- 那现在来分析这两个方法,它们是重载方法。(重载的定义:”方法名相同,但是参数列表不同”),根据定义是重载方法。但是现在这重载的方法后面的参数范围覆盖了第一个的参数范围。那么Java会运行哪一个呢?
- Java在编译时候会根据方法签名来确定调用哪个方法,比如 calPrice(499900,75,75),会调用第二个,但是只有两个会调用哪一个呢?也就是说到底会编译为哪一个呢?
- 经过运行之后,运行结果为: 简单折扣后的价格为:¥374.25
根据结果调用之后的结果,我们发现是调用第一个方法,为什么呢?因为int是一个原生数据类型,二数组本身是一个对象,编译器”偷懒”,会从简单的开始”猜想”,只要符合条件就可以通过。
三大特性
封装:将客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信任的类或者对象操作,对不可信的进行隐藏。简单的说就是一个类是一个封装了数据和操作这些数据的代码逻辑实体。
- 继承:指的是可以让某个类型的对象可以获取另一个类型的对象的属性的方法。继承指的是这样的一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
多态:指的是一个类实例的相同方法在不同的情形下有不同的表现形式。多态机制使得具有不同内部结构的对象可以共享相同的外部接口。这意味着:虽然针对不同对象的具体操作不同,但是可以通过一个公共的类,他们通过相同的方式进行调用。比如接口、抽象类、父类进行调用等等。
接口
使用关键字interface修饰的类,是抽象方法的集合。一个类通过实现接口的方式,从而可以实现不同的子类。
- 一个接口可以有多个方法。
- 接口的特性:
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法
- 抽象类和接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中不行。除非在java8以及以上版本,可以使用default修饰的默认方法
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口
- 抽象类和普通类的区别
仅仅是使用abstract进行修饰,且支持abstract修饰的方法为抽象方法。即没有实现的方法。其他的没有什么不同。
Java8对接口的优化
java7之前,接口不能编写默认的实现类,就是使用default修饰的方法。
- 但是java8之后,可以有默认实现的方法。
在java9之后,允许将接口中的方法定义为private
public interface InterfaceTest { public static final String A = "A"; String B = "B"; default void hello() { System.out.println("InterfaceTest.hello"); } public abstract void hello2(); void hello1(); }抽象类
抽象类
- 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
- 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
- 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
- 父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
- 在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
- 抽象方法
- 如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
- abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
- 抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
- 总结
- 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过【如果想要实例化,就必须实现其抽象方法,这时候就是匿名内部类】。只有抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
内部类
在类内部被声明的类
- 成员内部类
- 局部内部类
- 匿名内部类
静态内部类 ```java public class StaticClass {
// 成员内部类 // 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员) // 不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问 public class A {
}
public static class B {
}
public static final class C {
}
protected static class D {
}
private class E {
}
private static class F {
} }
<a name="xJHcN"></a>
# 匿名内部类
- 就是没有名字的内部类,一般用于实现抽象方法或者是接口
- 适用场景,就是这个类只使用几次,而且实现的功能也不一样,那么就只要在用的地方实现一下就好了。
- 接口的
```java
public interface InterfaceDemo {
void run();
}
class InterfaceDemoTest {
public static void main(String[] args) {
InterfaceDemo interfaceDemo = new InterfaceDemo() {
@Override
public void run() {
System.out.println("InterfaceDemoTest.run");
}
};
}
}
- 抽象类的 ```java public abstract class AbstractClassDemo { abstract void run(); }
class AbstractClassDemoTest { public static void main(String[] args) { AbstractClassDemo abstractClassDemo = new AbstractClassDemo() { @Override void run() { System.out.println(“AbstractClassDemoTest.run”); } }; } } ```
静态类
- 使用static修饰的类
- 如果一个类要被声明为static的,只有一种情况,就是静态内部类
- 静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法
- 静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法
- 静态内部类可以单独初始化
