1、 stdout、stdin、 stderr
- System.out:代表标准输出设备
- System.in: 代表标准输入设备
Scanner input = new Scanner(System.in); // 创建Scanner类型对象,读取来自System.in的输入
1、变量类型
public class Variable{
static int allClicks=0; // 类变量
String str="hello world"; // 实例变量
public void method(){
int i =0; // 局部变量
}
}
Java 局部变量(类似于C/C++中自动变量,但C/c++中没有初始化的自动变量值默认为0)
局部变量是在方法内部定义的变量,如果局部变量和一个类变量(实例变量或静态变量)名相同,局部变量优先。同名类变量将被隐藏,是为隐藏数据域
局部变量作用域在语句块内或方法内,但不可重复定义,会出错
局部变量声明在方法、构造方法或者语句块中;
局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;
访问修饰符不能用于局部变量;
局部变量只在声明它的方法、构造方法或者语句块中可见;
局部变量是在栈上分配的。
局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。
局部变量和
实例变量(类变量或数据域,类似于python中实例属性)
实例变量声明在一个类中,但在方法、构造方法和语句块之外;
当一个对象被实例化之后,每个实例变量的值就跟着确定;
实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
实例变量的作用域是整个类:实例变量可以声明在使用前或者使用后;
访问修饰符可以修饰实例变量;
实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定;
实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:ObejectReference.VariableName
静态变量(类变量或数据域),类似于python中的类属性
类变量也称为静态变量,在类中以 static 关键字声明,但必须在方法之外。
无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。
静态变量除了被声明为常量外很少使用,静态变量是指声明为 public/private,final 和 static 类型的变量。静态变量初始化后不可改变。
静态变量储存在静态存储区。经常被声明为常量,很少单独使用 static 声明变量。
静态变量在第一次被访问时创建,在程序结束时销毁。
静态变量的作用域是整个类。但为了对类的使用者可见,大多数静态变量声明为 public 类型。
默认值和实例变量相似。数值型变量默认值是 0,布尔型默认值是 false,引用类型默认值是 null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。
静态变量可以通过:ClassName.VariableName的方式访问。也可以使用ObejectReference.VariableName 访问,或者同一个类中直接使用VaribaleName方法
类变量被声明为 public static final 类型时,类变量名称一般建议使用大写字母。如果静态变量不是 public 和 final 类型,其命名方式与实例变量以及局部变量的命名方式一致。
变量的作用域
1、局部变量的作用域从声明变量的地方开始,直到包含该变量的块结束为止。Java和C/C++都有块作用域,而python则没有。
2、参数也是一个局部变量,方法的参数作用域覆盖整个方法。
3、for循环头中初始动作部分声明的变量,其作用域是整个for循环;而循环体内声明的变量,其作用域只限于循环体内从它的声明处开始到包含该变量的快结束位置:
4、可在不同的代码块中声明同名的局部变量;不能再且套块中或同一块中两次声明同一个局部变量(在java中这只会报错,而在C中则前者会被后者覆盖)。
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) { // i的作用域是整个for循环
System.out.println("i = " + i);
int j = 5; // j的作用域是到for循环块结束
System.out.println("j = " + j);
System.out.println("i = " + i);
}
}
}
2、修饰符
2.1、访问控制修饰符
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lfd99egm-1609685038156)(https://i.imgur.com/xhVFwp2.png)]
- default 默认访问修饰符:
对同一包内的类可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public
- private 私有访问修饰符(类似python的私有变量):
对象只能被所属类访问。使用对象:变量、方法。 注意:不能修饰类和接口
private声明的对象不能被子类继承,也不能被子类访问
隐藏类的实现细节和保护类的数据:
声明为私有访问的变量只能通过类中公共的getter方法访问和setter方法修改
- public 公有访问修饰符:
对所有类可见。使用对象:类、接口、变量、方法
类的所有公有方法和变量都能被子类继承
- protected 受保护的访问修饰符:
对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类和接口。
子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。
接口及接口的成员变量和成员方法不能声明为 protected:
2.1.1、 访问控制和继承
- 父类中声明为 public 的方法在子类中也必须为 public。
- 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
- 父类中声明为 private 的方法,不能够被继承。
2.3、 非访问修饰符
- static 修饰符,用来修饰类方法和类变量
声明静态变量和静态方法,它们是类所有,类及其实例对象都可访问它们,“类名.静态变量或方法”、“对象名.静态变量或方法”
声明静态变量(即类变量):
用于声明独立于对象的静态变量(类变量),无论实例多少对象,静态变量只有一份拷贝,静态变量为所有实例所共享。
静态变量存储在一个公共的内存地址:某个实例修改静态变量,那么同类的所有对象都会收到影响。-- 和python大大的不同:无法修改类变量
局部变量不能声明为static变量
声明静态方法:
声明独立于对象的静态方法,静态方法不能直接访问类的非静态变量或非静态方法
只能实例化类后,使用实例对象调用对象的非静态变量和非静态方法。
静态方法从参数列表得到数据,然后计算这些数据
public class Main {
int aa = 5;
public Main(int caa) {
aa = caa;
}
public static void main(String[] args) {
int[] intArr1 = null;
printData(intArr1);
int[] intArr2 = {1, 2, 3, 4, 5};
printData(intArr2);
Main cc = new Main(88);
System.out.println(cc.aa);
cc.test();
}
private static void printData(int[] intArr) {
if (intArr == null) {
System.out.println("資料異常");
return ;
}
}
public void test() {
System.out.println("1234");
}
}
- final 修饰符,
用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
- abstract 修饰符,用来创建抽象类和抽象方法(类似于python的抽象基类)
抽象性类不能用于实例化对象,抽象类可以包含抽象方法和非抽象方法,而抽象方法没有实现。继承抽象类的具体子类应实现所有抽象方法。
public abstract class SuperClass{
abstract void m(); //抽象方法
}
class SubClass extends SuperClass{
//实现抽象方法
void m(){
.........
}
}
- synchronized 修饰符
synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。
public synchronized void showDetails(){
.......
}
-
transient 修饰符
序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。 -
volatile 修饰符
volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
一个 volatile 对象引用可能是 null。
构造方法
每个类都有构造方法,如果没有显示的为类定义构造方法,Java编译器会为类提供一个默认的构造方法.
构造方法没有返回值类型,连void也没有
创建对象时会调用构一个构造方法,构造方法必须与类同名,一个类可以有多个构造方法(实例对象时,依据参数的不同调用不同的构造方法)
public class Puppy{
public Puppy(){
}
public Puppy(String name){
// 这个构造器仅有一个参数:name
}
创建对象
类是一种引用类型。当类的实例化对象引用计数为0时,就会被自动垃圾回收。
声明一个类的对象引用变量: “对象类型 对象名”。 引用变量没有引用任何对象时,默认值为null。
实例化创建一个对象: “new 对象类型(参数或无参数)”
初始化:使用new创建对象时,会调用构造函数初始化对象
public class Puppy{
public Puppy(String name){
//这个构造器仅有一个参数:name
System.out.println("小狗的名字是 : " + name );
}
public static void main(String[] args){
// 下面的语句将创建一个Puppy对象
Puppy myPuppy = new Puppy( "tommy" );
}
}
数组被看作是对象,因为使用new创建的。数组变量是包含数组引用的变量
String是在java中是类,所有也是创建对象。
访问实例变量和方法
通过已创建对象来访问成员变量和成员方法
/* 实例化对象 */
Object referenceVariable = new Constructor();
/* 访问类中的变量 */
referenceVariable.variableName;
/* 访问类中的方法 */
referenceVariable.methodName();
基本类型变量:对应内存所存储的是基本类型值
引用类型变量: 对应内存所存储的是一个引用,即对象的存储地址。为某个对象的引用变量赋null值,对象的引用计数减一。
源文件声明规则
一个源文件中只能有一个 public 类
一个源文件可以有多个非 public 类
如果一个类定义在某个包中,那么package语句应在源文件的行首
如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面
包的使用:组织类,区别类的namespace
this 引用
关键字this引用对象自身,this类似于python的self。
- 用this关键字引用对象的实例变量
public class Main {
private int i = 5;
public static void main(String[] args) {
Main cc = new Main();
System.out.println(cc.getter());
}
public int getter() {
return this.i; // 显式地引用对象实例变量成员,通常this是省略掉的
}
}
- 使用this引用隐藏数据域
隐藏数据域的两种情况:
1、指类变量(实例变量和静态变量都是类变量)和局部变量同名时,类变量将被隐藏。
2、数据域名和方法的参数名相同时时,该数据域名在该方法中也会被隐藏,因为实际操作的是作为局部变量的同名实参。
情况一,类变量和局部变量同名时,使用关键字可以引用对象的隐藏数据域:
public class Main() {
public static void main(String[] args) {
SimpleCircle circle = new SimpleCircle();
circle.p();
}
}
class SimpleCircle {
double radius;
SimpleCircle() {
radius = 1.0;
}
public void p() {
double radius = 5.0; // 局部变量和数据域同名,对象的数据域变量radisu将被隐藏
System.out.println(radius); // 显示局部变量radius的值
System.out.println(this.radius); // 使用this引用隐藏域变量radius
}
情况二,setf方法中,将数据域名作为参数,使用关键字可以引用对象的隐藏数据域:
public class Main() {
public static void main(String[] args) {
F f1 = new F();
f1.setI(2);
System.out.println(f1.i);
F f2 = new F();
f2.setI(13);
System.out.println(f2.i);
F.setK(33);
System.out.println(F.k);
}
}
class F {
public int i = 5;
public static double k = 0;
public void setI(int i) { // 参数名和数据域名i相同,数据域变量i将被隐藏
this.i = i; // 需要使用thsi引用隐藏的数据域名,才能给其设置新值
}
public static void setK(double k) { // 参数名和数据域名k相同
F.k = k; // 使用"类名.静态变量"的方式引用隐藏的静态变量k
}
}
setI方法使用和数据域名相同的参数名,这变导致数据域变量i被隐藏,要设置数据域变量必须显示的指定this引用对象自身。
如果参数名与数据域名不相同则不会导致数据域隐藏。
- 使用this让构造方法调用同一个类的另一个构造方法
public class Main {
public static void main(String[] args) {
F f = new F();
System.out.println(f.radius);
}
}
class F {
public double radius;
public F(double radius) {
this.radius = radius;
}
public F() {
this(1.0); // this关键字调用另一个重构的构造方法
}
}
构造方法中,this语句应在任何其它可指向语句之前出现
简化代码:无参数或参数少的构造方法应使用"this(参数列表)"调用参数多的构造方法
类中出现重名时,变量具有最高引用级别
一个变量或一个类型可以遮盖一个包
一个变量可以遮盖具有相同名字的一个类型,只要它们都在同一个范围内:
public class Main {
public static void main(String[] args) {
System.out.println(X.Y.Z); // 显示"White"
}
}
class X {
static C Y = new C(); // 变量Y会被优先引用,而不会是下面的类型Y
static class Y {
static String Z = "Black";
}
}
class C {
String Z = "White";
}
继承
extends:只要透過 extends 關鍵字,子類別將可以擁有父類別的所定義屬性欄位、方法等功能。
class 父类名{
// todo sth...
}
class 子类名 extends 父类名{
// todo sth...
}
super
super 关键字:通过 super 关键字來存取父类属性或调用父类的方法。
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
}
}
class Animal {
void eat() {
System.out.println("Aninal: eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("Dog: eat");
super.eat(); // 调用父类方法eat()
}
}
/* output
Dog: eat
Animal: eat
*/
与python对比:
python的super是超类代理对象,可以选中继承链中任何一个超类,并调用它的方法。
多层初始化
实例化对象时,如果是多层继承,则父类先初始化,再继续子类初始化
public class Main {
public static void main(String[] args) {
C c = new C();
}
}
class A{
public A(){
System.out.println("執行A構造方法...");
}
}
class B extends A{
public B(){
System.out.println("執行B構造方法...");
}
}
class C extends B{
public C(){
System.out.println("執行C構造方法...");
}
}
/* output
執行A構造方法...
執行B構造方法...
執行C構造方法...
*/
与python的比较:
python仅会自动调用当前子类的__init__构造方法,而不会调用父类的构造方法,你需要主动调用父类的构造方法。
静态绑定与动态绑定
绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来
绑定分为静态绑定和动态绑定
静态绑定使用的是类信息,而动态绑定使用的是对象信息
- 静态绑定
静态绑定发生编译时,由编译器执行。
java当中的方法只有final,static,private和构造方法都是静态绑定
java的属性(实例变量和静态变量)都是静态绑定(让我们在编译器就发现错误。提高程序运行效率)
一个方法不可被继承或者继承后不可被覆盖,那么这个方法就采用的静态绑定
- 动态绑定
在程序运行时根据具体对象的类型进行绑定。方法除了final,static,private
和构造方法是静态绑定外,其他的方法(虚方法)全部为动态绑定。
对方法采取动态绑定是为了实现多态,多态是java的一大特色。多态也是面向对象的关键技术之一,所以java是以效率为代价来实现多态这是很值得的。
而动态绑定的典型发生在父类和子类的转换声明之下:
比如:Parent p = new Children();
其具体过程细节如下:
1:编译器检查对象的声明类型和方法名。
假设我们调用x.f(args)方法,并且x已经被声明为C类的对象,那么编译器会列举出C 类中所有的名称为f 的方法和从C 类的超类继承过来的f 方法。
2:接下来编译器检查方法调用中提供的参数类型。
如果在所有名称为f 的方法中有一个参数类型和调用提供的参数类型最为匹配,那么就调用这个方法,这个过程叫做“重载解析”。
3:当程序运行并且使用动态绑定调用方法时,虚拟机必须调用同x所指向的对象的实际类型相匹配的方法版本。
假设实际类型为D(C的子类),如果D类定义了f(String)那么该方法被调用,否则就在D的超类中搜寻方法f(String),依次类推
若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为:它们都要在对象中安插某些特殊类型的信息。
动态绑定的过程:
1、虚拟机提取对象的实际类型的方法表;
2、虚拟机搜索方法签名;
3、调用方法。
动态绑定为解决实际的业务问题提供了很大的灵活性,是一种非常优美的机制。
验证属性是静态绑定,而虚函数方法是动态绑定:
在向上转型的情况下,对象的方法可以找到子类,而对象的属性(成员变量)还是父类的属性(子类对父类成员变量的隐藏)
public class Main {
public static void main(String[] args) {
Super sup = new Sub();
System.out.print(sup.field+" "+sup.getField());//对于域的访问由编译器决定(静态绑定)
}
}
class Super {
public int field=0; // 实例变量是静态绑定
public int getField() {
System.out.println("Super");
return field;
}
}
class Sub extends Super {
public int field=1;
public int getField() { // 该方法是动态绑定
System.out.println("Sub");
return field;
}
public int getSuperField(){
return super.field;
}
}
/* output
Sub
0 1
*/
向上转型和向下转型
Java转型的核心是父类引用指向子类对象(注意:子类引用不能指向父类对象),转型的作用是提升代码的可扩展性
向上转型(upcasting)
将子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转换:
Father f1 = new Son(); // Father是父类,Son是子类:这是 upcasting (向上转型),父类引用f1指向子类对象
向上原型后,父类引用指向子类对象:
JAVA 虚拟机调用一个类方法(静态方法)或类变量(静态变量或实例变量),它会基于对象引用的类型(通常在编译时可知)来选择所调用的方法或变量。相反,当虚拟机调用一个实例方法时,它会基于对象实际的类型(只能在运行时得知)来选择所调用的方法,这就是动态绑定,是多态的一种
向上转型时会遗失除与父类对象共有的其他方法:
public class Main {
public static void main(String[] args) {
Animal b = new Bird(); // 向上转型
b.eat();
//! error: b.fly(); b虽指向子类对象,但此时丢失fly()方法
}
}
class Animal {
public void eat(){
System.out.println("animal eatting...");
}
}
class Bird extends Animal{
public void eat(){
System.out.println("bird eatting...");
}
public void fly(){
System.out.println("bird flying...");
}
}
/* output
bird eatting...
*/
父类为形参,调用子类作为实参,就是利用了向上转型。这样使代码变得简洁,并体现Java多态和抽象的编程思想:
public class Main {
public static void main(String[] args) {
sleep(new Male()); // 向上转型:实参是子类,而形参是父类
sleep(new Female());
}
public static void sleep(Human h) { /* 参数是父类*/
h.sleep();
}
}
class Human {
public void sleep() {
System.out.println("父类人类 sleep..");
}
}
class Male extends Human {
@Override
public void sleep() {
System.out.println("男人 sleep..");
}
}
class Female extends Human {
@Override
public void sleep() {
System.out.println("女人 sleep..");
}
}
/* output
男人 sleep..
女人 sleep..
*/
向下转型(downcasting)
将指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转换:
Father f1 = new Son();
Son s1 = (Son)f1; // 这是 downcasting (向下转型)
public class Main {
public static void main(String[] args) {
Super sup = new Sub();
Sub sup1 = (Sub)sup; // 安全的向下转型,将父类引用对象转为子类对象
System.out.println(sup1.field); // 调用的子类的属性
System.out.println(sup1.getSuperField()); // 调用子类的方法
System.out.println(sup1.getField()); // 调用的是子类的方法
}
}
class Super {
public int field=0;
public int getField() {
System.out.println("Super");
return field;
}
}
class Sub extends Super {
public int field=1;
public int getField() {
System.out.println("Sub");
return field;
}
public int getSuperField(){
return super.field;
}
}
/* output
1
0
Sub
1
*/
- 不安全的向下转型
Fruit f=new Fruit();
Apple aaa=(Apple)f; //-不安全的---向下转型,编译无错但会运行会出错
aaa.myName();
aaa.myMore();
f是父类对象,子类的实例aaa肯定不能指向父类f啊~~~
Java为了解决不安全的向下转型问题,引入泛型的概念
为了安全的类型转换,最好先用 if(A instanceof B) 判断一下
虚函数
虚函数的存在是为了多态。
C++中普通成员函数加上virtual关键字就成为虚函数
Java中其实没有虚函数的概念,它的普通函数就相当于C++的虚函数,动态绑定是Java的默认行为。如果Java中不希望某个函数具有虚函数特性,可以加上final关键字变成非虚函数
Java抽象函数(纯虚函数)
抽象函数或者说是纯虚函数的存在是为了定义接口。
C++中纯虚函数形式为:virtual void print() = 0;
python中纯虚函数形式为: @abstractmethod装饰的函数,也称抽象方法
Java中纯虚函数形式为:abstract void print();
Java抽象类
Java抽象类的存在是因为父类中既包括子类共性函数的具体定义,也包括需要子类各自实现的函数接口。抽象类中可以有数据成员和非抽象方法。
C++中抽象类只需要包括纯虚函数,既是一个抽象类。如果仅仅包括虚函数,不能定义为抽象类,因为类中其实没有抽象的概念。 python中抽象基类只需要包括纯虚函数(即抽象方法),就是一个抽象基类。
Java抽象类是用abstract修饰声明的类。
PS: 抽象类其实是一个半虚半实的东西,可以全部为虚,这时候变成接口。
Java接口
Java中接口的存在是为了形成一种规约。接口中不能有普通成员变量,也不能具有非纯虚函数。
使用关键字interface定义一个接口:
public interface Info{ // 修饰符 interfase 接口名 {
final int AA = 5; /** 常量声明 **/
void getInfo(); /** 方法签名 **/
} //}
使用关键字implements让类实现接口
class 类名 implements 接口1,接口2,...接口x{
//
}
接口定义和实现的代码示范:
interface A {
void getAInfoData();
}
interface B {
void getBInfoData();
}
class Main implements A, B {
public static void main(String[] args) {
}
@Override
public void getAInfoData() {
}
@Override
public void getBInfoData() {
}
}
接口继承接口
接口也是类,也有继承特性。可以将多个接口组合成一个接口,该接口可被简单使用
public interface 接口名 extends 接口1, 接口2 {
// ...
}
代码示范:
interface A {
void getAData();
}
interface B extends A {
void getBData();
}
public class Main implements B { // 等同 接口定义和实现的代码示范中的"implements A, B"
public static void main(String[] args) {
}
@Override
public void getBData() {
}
@Override
public void getAData() {
}
}
C++中接口其实就是全虚基类。
python中接口就是抽象基类, 或鸭子类型。
Java中接口是用interface修饰的类,。