文章目录
面向对象编程
基础篇
方法
-
每个类中含有多个域,但我们将域设置为
public
时,域就会暴露给外部从而破坏封装性,从而造成逻辑混乱。为了避免外部代码直接访问类的域,我们将其设置为private
,此时要想访问域,则需要通过方法来操作;public class Main { public static void main(String[] args) { Person k = new Person(); k.setName("Cunyu"); // 设置name k.setAge(25); // 设置age System.out.println(k.getName() + ", " + k.getAge()); } } class Person { // 直接设置为public,然后直接操作域,则会编译报错 // public String name; // public int age; // 设置为private,然后通过方法访问 private String name; private int age; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getAge() { return this.age; } public void setAge(int age) { if (age < 0 || age > 100) { throw new IllegalArgumentException("invalid age value"); } this.age = age; } }
-
定义方法的语法如下,需要注意的是定义
private
方法是方便内部方法调用;修饰符 方法返回类型 方法名(方法参数列表){ // 实现方法功能的语句 ... return 方法返回值; }
-
this
变量方法内部使用隐含变量
this
,它始终指向当前实例,通过this
访问当前实例的字段从而避免命名冲突; -
方法参数
方法可以有
0
或任意个参数,用于传递给方法变量值,通过调用方法,按照其定义严格传递; -
可变参数
通过
类型 ...
定义,相当于数组类型;class Group{ private String[] names; public void setName(String... names){ // 等价于 // public void setName(String[] names){ this.names = names; } }
-
参数绑定
- 使用基本数据类型做方法形参时,在方法体中对形参进行修改不会影响实参数值;
- 使用引用数据类型作方法形参时,若在方法体中修改形参指向数据内容,会对实参变量数值产生影响,因为形参和实参指向同一对象;
- 使用引用数据类型作方法形参时,若在方法体中修改形参的指向,不会对实参变量数值产生影响,因为形参和实参指向不同对象;
构造方法
在进行初始化对象实例时,为了将其内部字段初始化,这时则需要构造方法来初始化实例。构造方法的名称是类名,其参数也没有限制,但是没有返回值,调用时必须使用new
操作符;其次,构造方法中引用类型字段都默认为null
,int
默认为0
,布尔类型默认为false
;构造方法之间也可以相互调用;
方法重载
当在一个类中需要定义多个功能类似的方法,但其参数不同,则将这一系列方法叫做同名方法,这种方法名相同,但各自参数不同称为方法重载(overload),其返回值类型一般相同;
class Demo{
public void hello(){
System.out.println("hello, Java!");
}
public void hello(String name){
System.out.println("hello, " + name + "!");
}
}
继承
-
为了提高代码复用率,常利用继承从父类获得所有功能,通过
extends
关键字来实现继承;class Person { private String name; private int age; public String getName() {...} public void setName(String name) {...} public int getAge() {...} public void setAge(int age) {...} } class Student extends Person { // 不要重复name和age字段/方法, // 只需要定义新增score字段/方法: private int score; public int getScore() { … } public void setScore(int score) { … } }
-
继承树
Java中所有类均继承自
Object
,只允许单继承,即一个类有且只有一个父类,而Object
无父类; -
protected
子类无法访问父类
private
修饰的字段或方法,从而使得继承作用大大削弱,为了解决这个问题,可以将父类中的private
改为protected
,方便子类使用; -
super
super
表示父类,子类引用父类字段时,可以使用super.fieldName
,子类调用父类构造方法是,必须显式调用super()
,子类不会继承任何父类构造方法;class Student extends Person { public String hello() { return "Hello, " + super.name; } }
-
向上&下转型
向上转型即将子类安全转换为父类类型的赋值,而向下转型则是将父类类型强制转换为子类类型,向下转型时最好利用
instanceof
先进行判断; -
继承和组合
继承是
is
关系,而组合则是has
关系;
多态
继承关系中,若子类定义了与父类方法签名完全相同的方法,则被称为重写(Override);
**PS:**方法名相同,方法参数相同,方法返回值不同,也属于不同方法;
重写和重载的区别:若方法签名不同,则是重载,重载是一个新的方法;若方法签名相同且返回值也相同,则是重写;
多态:针对某类型的方法调用,真正执行的方法却决议运行时实际类型的方法;
final
:继承允许子类重写父类方法,若父类不允许子类对某一方法进行重写,则课将该方法标记为final
,final
修饰的方法无法被重写;
class Person {
protected String name;
public final String hello() {
return "Hello, " + name;
}
}
Student extends Person {
// compile error: 不允许重写
@Override
public String hello() {
}
}
抽象类
将方法声明abstract
,则表示它是一个抽象方法,本身无具体执行代码,此时必须将包含抽象方法的类也声明为abstract
,值得注意的是:抽象类无法实例化;
public class Main{
public static void main(String[] args){
Person p = new Person();
p.run();
}
}
abstract class Person{
public abstract void run();
}
class Student extends Person{
@Override
public void run(){
System.out.println("Student.run");
}
}
- 面向抽象编程的本质
- 上层代码只定义规范;
- 无需子类即可正常编译;
- 方法的具体实现由不同子类实现,调用者不用关心;
- 总结
abstract
定义的方法只是抽象方法,只有定义,没有实现,此外,抽象方法还定义了子类必须实现的接口规范;- 从抽象类继承来的子类必须实现抽象方法;
- 若不实现抽象方法,则该子类仍是抽象类;
接口
-
抽象方法的本质是定义接口规范,但若一个抽象类中无字段而只有抽象方法,则可以将该类改写为接口
interface
,具体的类要实现接口时,使用关键字implements
;interface Person{ void run(); String getName(); } class Student implement Person{ private String name; public Student(String name){ this.name = name; } @Override public void run(){ System.out.println(this.name + "run"); } @Override public String getName(){ return this.name; } }
-
Java中,每个类只能继承一个类,无法从多个类继承,但一个类却可以实现多个
interface
; -
接口之间也可以相互继承,接口之间继承使用
extends
关键字;interface Demo{ void hello(); } interface Demo1 extends Demo{ void run(); String getName(); }
-
继承关系
抽象类和接口对比
abstract class |
interface |
|
---|---|---|
继承 | 只能extends 一个class |
可implements 多个interface |
字段 | 可以定义实例字段 | 不能定义实例字段 |
抽象方法 | 可以定义抽象方法 | 可定义抽象方法 |
非抽象方法 | 可定义非抽象方法 | 可定义default 方法 |
静态字段和方法
-
类中定义的字段称为实例字段,其特点是每个实例均有独立字段,各实例的同名字段互不影响。实例字段在每个实例中具有自己的独立“空间”;
-
static
修饰的字段叫做静态字段,静态字段只有一个共享“空间”,所有实例均共享该字段;class Person{ // 实例字段 public String name; public int age; // 静态字段 public static int number; }
-
不推荐用
实例变量.静态字段
去访问静态字段,因为Java中实例对象无静态字段,实际上能访问是因为编译器可以根据实例类型自动转换为类名.静态字段
来访问静态对象; -
静态方法:
static
修饰的方法,调用实例方法需要实例变量,但调用静态方法不需要实例变量,只需要通过类名即可调用;静态方法不属于实例,因此在静态方法内部无法访问this
变量,同时也无法访问实例字段,只能访问静态字段;public class Main{ public static void main(String[] args){ Person.setNumber(99); System.out.println(Person.number); } } class Person{ // 静态字段 public static int number; // 静态方法 public static void setNumber(int value){ number = nvalue; } }
-
接口的静态字段
interface
属于纯抽象类,所以无法定义实例字段,但是可以有静态字段,且接口中的静态字段必须为final
类型;public interface Person{ public static final int MALE = 2; public static final int FEMALE = 1; }
包
-
一个类总是属于一个包:
package
,类名只是简写,完整的类名应该是包名.类名
,包属于多层结构,不同层之间用.
隔开;而且包之间不存在父子关系; -
当需要引用其他类时,通常有三种写法:
- 直接写出完整类名;
- 通过
import
语句; import static
,导入一个类的静态字段和方法;
package ming; // 完整类名 public class Person{ public void run(){ mr.jun.Arrays arrays = new mr.jun.Arrays(); } }
package ming; import mr.jun.Arrays; // import public class Person{ public void run(){ Arrays arrays = new Arrays(); } }
package ming; import static java.lang.System.*; // import static public class Person{ public void main(String[] args){ out.println("Hello, Java"); } }
-
类的查找顺序
- 若是完整类目,直接查找对应类;
- 若是简单类名,先查找当前
package
是否存在这个类; - 若是简单类名,查找
import
的包是否包含此类; - 若是简单类名,查找
java.lang
包是否包含此类;
-
创建新类时,默认的
import
动作- 默认
import
当前package
的其他class
; - 默认
import java.lang.*
;
- 默认
作用域
作用域 | 当期类 | 同一package | 子孙类 | 其他package |
---|---|---|---|---|
public | ✔ | ✔ | ✔ | ✔ |
protected | ✔ | ✔ | ✔ | ✖ |
private | ✔ | ✖ | ✖ | ✖ |
friendly | ✔ | ✔ | ✖ | ✖ |
- 局部变量:方法内部定义的变量,局部变量作用域从变量声明处开始到对应的块结束,注意方法参数也属于局部变量;
classpath和jar
classpath
是JVM
用到的环境变量,用于指示JVM
如何搜索class
;
模块
Java核心类
字符串及编码
-
String
字符串在
String
内部通过char[]
数组实现,最重要的一个特点是不可变;public class Main{ public static void main(String[] args){ String s1 = "hello"; String s2 = "hello"; // 字符串比较 if(s1.equals(s2)){ System.out.println("s1 == s2"); } // 去除首尾空白字符 System。out.println(" helloJava ".trim()); // 替换子串 String s3 = "hello"; s.replace('o', 'w'); // 字符串分割 String s4 = "A,B,C,D"; String[] ss = s4.split("\\,"); // 字符串拼接 String[] arr = {"a", "b", "c"}; String s5 = String.join("**", arr); // 其他类型转换为字符串 String.valueOf(123); String.valueOf(true); // String和char[]相互转换,此时改变char[]数组时,不会改变String char[] chArray = "hello".toCharArray(); String s6 = new String(chArray); } }
-
字符编码
Java使用Unicode编码表示
String
和char
;编码转换是将String
和byte[]
进行转换,转换时需要指定编码;
StringBuilder
String
会在每次循环过程中创建新的字符串对象,然后扔掉旧字符串,造成内存浪费。为此,提供StringBuilder
,这是个可变对象,可以预分配缓冲区,对它进行操作时不会创建新的临时对象,同时也支持链式操作;
StringBuilder sb =new StringBuilder(1943);
for (int i = 0; i < 1000; i ++){
sb.append(',');
sb.append(i);
}
String s = sb.toString();
StringJoiner
包装类型
基本类型 | 对应的引用类型 |
---|---|
boolean |
java.lang.Boolean |
byte |
java.lang.Byte |
short |
java.lang.Short |
int |
java.lang.Integer |
long |
java.lang.Long |
float |
java.lang.Float |
double |
java.lang.Double |
char |
java.lang.Character |
-
自动装/拆箱(Auto Boxing/Unboxing)
自动装箱是指将基本类型变为对应引用类型,自动拆箱是指将对应的引用类型转换为基本类型;
JavaBean
-
通常将一组对应的读方法(
getter
)和写方法(setter
)称之为属性(property
),需要注意的是boolean
字段的读方法一般命名为isXyz()
,具有上述熟悉的类叫做JavaBean
;// 一般类型 // 读方法 public Type getXyz() // 写方法 public void setXyz(Type value)
-
作用
用于传递数据,即将一组数据组合为一个
JavaBean
便于传输。此外也方便IDE
进行分析,生成读写属性的代码,主要用于图形界面可视化设计; -
枚举
JavaBean
属性当需要枚举一个
JavaBean
的所有属性时,可利用核心库提供的Introspector
,利用Introspector.getBeanInfo()
获取属性列表;public class Main { public static void main(String[] args) throws Exception { BeanInfo info = Introspector.getBeanInfo(Person.class); for (PropertyDescriptor pd : info.getPropertyDescriptors()) { System.out.println(pd.getName()); System.out.println(" " + pd.getReadMethod()); System.out.println(" " + pd.getWriteMethod()); } } } class Person { private String name; private int age; 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; } }
枚举类
-
为了编译器能自动检查某一值在枚举的集合内,且不同用途的枚举用不同类型来标记,使用
enum
来定义枚举类;public class Main { public static void main(String[] args) { Weekday day = Weekday.SUN; if (day == Weekday.SAT || day == Weekday.SUN) { System.out.println("Work at home!"); } else { System.out.println("Work at office!"); } } } enum Weekday { SUN, MON, TUE, WED, THU, FRI, SAT; }
-
enum
类型的特点- 定义的
enum
类型继承自java.lang.Enum
,且无法被继承; - 只能定义出
enum
的实例,且无法通过new
操作符来创建新实例; - 定义的每个实例都是引用类型的唯一实例;
enum
类型课用于switch
语句;
- 定义的
BigInteger & BigDecimal
-
BigInteger
可以用于表示任意大小的整数,而且它属于不变类,继承自Number
,转换为基本类型的方法如下,转换时可通过longValueExact()
等方法保证准确;类型 方法 byte
byteValue()
short
shortValue()
int
intValue()
long
longValue
float
floatValue
double
doubleValue
-
BigDecimal
用于表示一个任意大小且精度完全准确的浮点数;import java.math.BigDecimal; import java.math.RoundingMode; public class Main { public static void main(String[] args) { BigDecimal d1 = new BigDecimal("123.456789"); BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568 BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567 // 计算小数位数; System.out.println(d1.scale); System.out.println(d2); System.out.println(d3); } }
-
BigDecimal
进行比较时,使用equals()
方法比较时要求两值相等,且小数位数scale()
也相同。所以总是使用compareTo()
进行BigDecimal
值得比较;
常用工具类
-
Math
:提供大量静态方法用于实现数学计算;常用方法 功能 abs
求绝对值 min
求最小值 max
求最大值 sqrt
求二次方根 exp
求幂 log 求以 e
为底的对数log10 求以10为底的对数 random
生成随机数 -
Random
:用于创建伪随机数(即只要给定一个初始种子,产生的随机数序列则是完全一致的);
生成随机数类型 | 方法 |
---|---|
int |
nextInt() |
long |
nextLong() |
float |
nextFloat |
double |
nextDouble |
-
SecureRandom
:用于创建真随机数(即不可预测的安全的随机数),通过操作系统提供的安全的随机种子来生成随机数;import java.util.Arrays; import java.security.SecureRandom; import java.security.NoSuchAlgorithmException; public class Main { public static void main(String[] args) { // 初始化一个对象并初始化为null SecureRandom sr = null; try { sr = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器 } catch (NoSuchAlgorithmException e) { sr = new SecureRandom(); // 获取普通的安全随机数生成器 } byte[] buffer = new byte[16]; sr.nextBytes(buffer); // 用安全随机数填充buffer System.out.println(Arrays.toString(buffer)); } }