Skip to content

从C++快速入门Java

约 1929 个字 278 行代码 预计阅读时间 10 分钟

本章内容建立在掌握C++的基础上,为从C++到Java的快速入门

基础

首先回答一个问题,为什么Java的main要写在类里面?这个是第一次看到Java程序会觉得很奇怪的写法

这主要是因为Java中一切皆对象,不允许将方法或者变量暴露在类外

Java语言的语法和C/C++很接近,所以最基础的基本看一下就会了,这里只列举一些不太一样的东西: * Java源文件(.java)是先编译成字节码(.class文件),再由Java虚拟机(JVM)解释执行,此外,JVM的即时编译器(JIT)会在运行时将热点代码(频繁执行的代码)编译为机器码,以提高性能。所以Java比纯解释的代码比如Python要快,但是一般又比纯编译的语言比如C++要慢。 * 主函数:Java里面是写成public static void main(String[] args),这个函数签名是固定的,类似于C的int main(),它也是Java程序的入口程序 * 输入输出 * 输出:System.out.printlnSystem.out.printf * 输入:需要用到Scanner类型,要先导入java.util.Scanner模块,比如:

import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner scanner=new Scanner(System.in);
        String name=scanner.nextLine(); //读取一行输入
        int age=scanner.nextInt(); //读取一行输入并获取整数
        //如果没有下一行代码,可能会警告:Resource leak: 'scanner' is never closed
        scanner.close(); // 手动关闭
        System.out.printf("%s的年龄是%d",name,age);

    }
}
//输入:nana
//      18
//输出:nana的年龄是18
* 布尔类型是boolean,不能对boolean进行类型转换 * 数组的写法略有不同(其实写成C++那样也可以过编译,但是不推荐,像下面那样写更符合Java风格: * 定义:要用new创建,int[] arr=new int[5] * 遍历:Java也有那个for each形式的语法for(int item:arr),但注意Java不提供C++里面那种auto自动推导类型,所以要写明类型 * 排序,java.util.Arrays中提供了Arrays.sort() * 输出,Java直接输出数组会输出哈希码,需要先用Arrays.toString()转化成字符串

import java.util.Scanner;
import java.util.Arrays;

public class Main{
    public static void main(String[] args){
        int [] arr=new int[10];
        Scanner scanner=new Scanner(System.in);
        for(int i=0;i<arr.length;i++){
            arr[i]=scanner.nextInt();
        }
        scanner.close();
        System.out.print("输出是一个哈希码:");
        System.out.println(arr);
        for(int item:arr){
            System.out.printf("%d ",item);
        }
        System.out.print("\n");
        Arrays.sort(arr);
        //注意是用Array提供的toString而不是写成arr.toString()
        System.out.println(Arrays.toString(arr));
    }
}
//输入1 2 3 4 5 6 7 8 9 10
/*输出
输出是一个哈希码:[I@6ea12c19
1 2 3 4 5 6 7 8 9 10
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
*/
  • 常量使用final
  • Java有垃圾回收机制,所以根本不需要手动管理内存,自然也就没有delete之类的东西
  • Java多了一个文档注释/** */,这种注释可以被工具提取并生成API文档
  • Java也没有指针,提供了引用,除了基本类型的变量名应该都是引用,在传参时也都是引用传递
  • 类型转换
  • 低转高是自动的
  • 高转低必须强制类型转换

面向对象

封装

这部分也是和C++高度相似,Java同样也是有privatepublic以及protected修饰符,有this变量指向当前对象,构造函数与类同名且没有返回值(但是Java要写public),通过new创建新的对象。

继承

Java提供了extends关键字来实现继承,所以没有C++里面的那种私有继承,所有继承一律都是public的。Java可以通过super调用父类方法,可以用final禁止继承。Java不支持多继承

Java有一个万物起源的类,也就是object,Java中一切类(除了object本身)都是继承自object

和C++一样,upcasting永远是安全的,downcasting大概率会失败,Java提供instanceof来检验一个变量是否是指定类型。

多态

Java支持函数重载,但不支持运算符重载,Java的Override提供了@Override进行覆写检查,

Java所有方法都相当于C++里面的虚函数,支持全面的动态绑定

//基类
class Animal{
    public void makeSound(){
        System.out.println("Animal is making a sound");
    }
}

//派生类1
class Dog extends Animal{
    @Override
    public void makeSound(){
        System.out.println("Dog is barking");
    }
}

//派生类2
class Cat extends Animal{
    @Override
    public void makeSound(){
        System.out.println("Cat is meowing");
    }
}

public class Main{
    public static void main(String[] args){
        //Upcasting
        Animal myAnimal=new Animal();
        Animal myDog=new Dog();
        Animal myCat=new Cat();

        myAnimal.makeSound();
        myDog.makeSound();
        myCat.makeSound();
    }
}
//结果:
/*
Animal is making a sound
Dog is barking
Cat is meowing
*/

接口

Java中抽象类用abstract修饰,抽象类是不能被实例化的类,通常用于作为其他类的基类。它可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法)。

abstract class Animal{
    //抽象方法
    public abstract void makeSound();
    //具体方法
    public void sleep(){
        System.out.println("Sleeping...");
    }
}

class Dog extends Animal{
    @Override
    public void makeSound(){
        System.out.println("woof");
    }
}
Java有接口类interface,接口是一种完全抽象的类,它只能包含抽象方法(在Java 8之前)和常量(public static final变量)。从Java 8开始,接口可以包含默认方法(default methods)和静态方法(static methods)。
interface Animal {
    void makeSound();

    default void sleep(){
        System.out.println("Sleeping...");
    }

    static void breathe(){
        System.out.println("Breathing...");
    }
}

相当于C++的命名空间,把功能相似或相关的类或接口组织在一个包中,方便类的查找和使用,不同包中的类名字可以相同。比如:

//package必须写在最前面,除了注释
package net.java.util;
public class Something{
  ...
}
那么它的路径就应该是net/java/Something.java

但是源文件是有声明规则的,当在一个源文件中定义多个类,并且还有importpackage语句时,需要特别注意这些规则:

  • 一个源文件只能有一个public类,可以有多个非public类,源文件名称应与public类保持一致
  • 如果一个类定义在某个包中,package语句应该放在第一行,import语句紧随其后
  • importpackage都是全局有效的,在同一个源文件中,不能给不同类不同的包声明

反射

这是一个新概念,是一个强大的特性,提供了动态操作类的能力,允许程序在运行时查询、访问和修改类、接口、字段和方法的信息。通常有以下工作流程: * 获取Class对象:首先获取目标类的Class对象 * 获取成员信息:通过Class对象,可以获取类的字段、方法、构造函数等信息 * 操作成员:通过反射API可以读取和修改字段的值、调用方法以及创建对象

Java提供了以下API:

  • java.lang.Class:表示类的对象
  • java.lang.reflect.Fielf:表示类的对象
  • java.lang.reflect.Method:表示类的方法
  • java.lang.reflect.Constructor:表示类的构造函数

但是反射也有缺点:

  • 破坏了封装性:你都能从外面获取类内部的信息了,可见封装被破坏了
  • 性能开销大:反射的性能比直接写要差很多,因为是动态执行的,不好优化

img

class是由JVM在运行过程中动态加载的,JVM在读到一种class类型时,将其加载进内存,每加载一个classJVM就为它创建一个Class实例,并关联起来,一个Class实例包含了class的全部信息,所以如果我们获取了一个Class实例,那么class也就已知了,这个过程就叫做反射。

Java由于版本更新,这部分很多方法都已经弃用或者改进了,网上很多教程都过时了,建议真的得去编译器试试。

获取Class

有三种方法获取Class

// 直接访问 class 的 class 属性
Class<?> cls1 = String.class;
// 通过实例变量获取
String s = "Hello";
Class<?> cls2 = s.getClass();
// 知道完整类名
Class<?> cls3 = Class.forName("java.lang.String"); // 注意大小写
// Class 实例在 JVM 中是唯一的
boolean is_same = (cls1 == cls2); // true
System.out.println("cls1 == cls2: " + is_same); // 输出: cls1 == cls2: true

创建新对象

// 获取 String 的 Class 实例
Class<?> cls = String.class;

// 使用无参构造函数创建 String 实例
String s1 = (String) cls.getDeclaredConstructor().newInstance();
System.out.println("s1: " + s1); // 输出: s1: 

// 如果需要使用带参构造函数,可以这样做:
Constructor<?> Constructor = cls.getConstructor(cls);
String s2 = (String) Constructor.newInstance("Hello, World!");
System.out.println("s2: " + s2); // 输出: s3: Hello, World!

获取类的信息

Class<?> clazz = String.class;

// 获取类名
System.out.println("类名: " + clazz.getName()); // 输出: java.lang.String
System.out.println("简单类名: " + clazz.getSimpleName()); // 输出: String

// 获取修饰符
int modifiers = clazz.getModifiers();
System.out.println("修饰符: " + Modifier.toString(modifiers)); // 输出: public final

// 获取父类
Class<?> superClass = clazz.getSuperclass();
System.out.println("父类: " + superClass.getName()); // 输出: java.lang.Object

// 获取实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> inter : interfaces) {
    System.out.println("接口: " + inter.getName());
}

获取字段

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

Class<?> clazz = Person.class;

// 获取所有 public 字段
Field[] fields = clazz.getFields();
for (Field field : fields) {
    System.out.println("字段: " + field.getName()); // 输出: age
}

// 获取所有字段(包括 private)
Field[] allFields = clazz.getDeclaredFields();
for (Field field : allFields) {
    System.out.println("字段: " + field.getName()); // 输出: name, age
}

// 访问和修改字段的值
Person person = new Person();
Field ageField = clazz.getField("age");
ageField.set(person, 25); // 设置 age 的值为 25
System.out.println("age: " + ageField.get(person)); // 输出: 25

// 访问 private 字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 设置可访问
nameField.set(person, "Alice");
System.out.println("name: " + nameField.get(person)); // 输出: Alice

获取方法

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    private void print(String message) {
        System.out.println(message);
    }
}

Class<?> clazz = Calculator.class;

// 获取所有 public 方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
    System.out.println("方法: " + method.getName());
}

// 获取所有方法(包括 private)
Method[] allMethods = clazz.getDeclaredMethods();
for (Method method : allMethods) {
    System.out.println("方法: " + method.getName());
}

// 调用 public 方法
Calculator calculator = new Calculator();
Method addMethod = clazz.getMethod("add", int.class, int.class);
int result = (int) addMethod.invoke(calculator, 10, 20);
System.out.println("add 结果: " + result); // 输出: 30

// 调用 private 方法
Method printMethod = clazz.getDeclaredMethod("print", String.class);
printMethod.setAccessible(true); // 设置可访问
printMethod.invoke(calculator, "Hello, Reflection!"); // 输出: Hello, Reflection!

异常

这个也是和C++几乎是一样的,这里给一个自定义异常的例子

// 自定义异常类
class MyCustomException extends Exception{
    public MyCustomException(String message){
      super(message);
    }
  }

  public class Main{
    public static void main(String[] args){
      try{
        //调用一个抛出异常的方法
        checkAge(16);
      }catch(MyCustomException e){
        System.out.println("捕获到异常:"+e.getMessage());
      }
    }

    public static void checkAge(int age) throws MyCustomException{
      if(age<18){
        throw new MyCustomException("年龄不能小于18岁");
      }
      else{
        System.out.println("年龄合法");
      }
    }
  }
// 输出:捕获到异常:年龄不能小于18岁

数据结构

Java 提供了丰富的数据结构,类似于 C++ 的标准模板库(STL)。以下是 Java 中常见的数据结构及其与 C++ 的类比关系:

  • Arrays:类似于 C++ 的静态数组。
  • ArrayList:类似于 C++ 的 vector,即动态数组。
  • LinkedList:双向链表。
  • Set
  • HashSet:无序集合,底层实现为哈希表。
  • TreeSet:有序集合,底层实现为红黑树,不允许重复元素。
  • Map
  • HashMap:无序映射,底层实现为哈希表。
  • TreeMap:有序映射,底层实现为红黑树。
  • Stack:类似于 C++ 的栈。
  • Queue:类似于 C++ 的队列。Java 还提供了优先队列(PriorityQueue)和双端队列(Deque)。
  • TreeNode:Java 提供了树的节点类型,可能是由于 Java 没有指针,直接提供了树的节点类型。
  • 枚举:Java 的枚举是类

Java 同样实现了常见的算法和迭代器

// 使用泛型的 ArrayList 示例
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");

// 使用泛型的 HashMap 示例
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "One");
map.put(2, "Two");

泛型

Java 的泛型(Generics)类似于 C++ 的模板(Templates),允许开发者编写通用的、类型安全的代码。通过泛型,可以在编译时进行类型检查,减少运行时错误。

泛型类

//泛型类
public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

泛型函数

//泛型函数
public <T> void printArray(T[] array) {
    for (T element : array) {
        System.out.println(element);
    }
}

通配符与边界

通配符

  • <?>:表示任意类型
  • <? extend T>:表示T或者T的子类型
  • <? super >:表示T或者T的父类型

边界

可以限制参数的范围

public <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}